146 lines
4.4 KiB
TypeScript
146 lines
4.4 KiB
TypeScript
export type CityPartnerScopeType = 'CITY_WIDE' | 'DISTRICT';
|
||
export type CityPartnerStatus = 'ACTIVE' | 'PAUSED';
|
||
|
||
export interface PartnerCityBindingInput {
|
||
id?: string;
|
||
partnerAccountId: string;
|
||
scopeType: CityPartnerScopeType;
|
||
districtCodes?: string[] | null;
|
||
bindingStatus?: CityPartnerStatus;
|
||
}
|
||
|
||
export interface PartnerCityResolveRef {
|
||
id: string;
|
||
partnerAccountId: string;
|
||
scopeType: CityPartnerScopeType;
|
||
orderCommissionRate: number;
|
||
redeemCommissionRate: number;
|
||
}
|
||
|
||
export interface PartnerCityValidationResult {
|
||
ok: boolean;
|
||
message?: string;
|
||
}
|
||
|
||
function normalizeDistrictCodes(codes?: string[] | null): string[] {
|
||
if (!codes?.length) return [];
|
||
return [...new Set(codes.map((c) => String(c).trim()).filter(Boolean))];
|
||
}
|
||
|
||
/** 区县 adcode 或名称命中区域合伙;否则全城合伙 */
|
||
export function resolveOrderCityPartner(
|
||
bindings: Array<{
|
||
id: string;
|
||
partnerAccountId: string;
|
||
scopeType: CityPartnerScopeType;
|
||
districtCodes?: string[] | null;
|
||
orderCommissionRate: number;
|
||
redeemCommissionRate: number;
|
||
bindingStatus?: CityPartnerStatus;
|
||
}>,
|
||
receiverDistrict?: string | null,
|
||
): PartnerCityResolveRef | null {
|
||
const active = bindings.filter((b) => b.bindingStatus !== 'PAUSED');
|
||
if (!active.length) return null;
|
||
|
||
const districtKey = receiverDistrict?.trim();
|
||
if (districtKey) {
|
||
const districtHit = active.find((b) => {
|
||
if (b.scopeType !== 'DISTRICT') return false;
|
||
const codes = normalizeDistrictCodes(b.districtCodes);
|
||
return codes.some((code) => code === districtKey || districtKey.includes(code) || code.includes(districtKey));
|
||
});
|
||
if (districtHit) {
|
||
return {
|
||
id: districtHit.id,
|
||
partnerAccountId: districtHit.partnerAccountId,
|
||
scopeType: districtHit.scopeType,
|
||
orderCommissionRate: districtHit.orderCommissionRate,
|
||
redeemCommissionRate: districtHit.redeemCommissionRate,
|
||
};
|
||
}
|
||
}
|
||
|
||
const cityWide = active.find((b) => b.scopeType === 'CITY_WIDE');
|
||
if (cityWide) {
|
||
return {
|
||
id: cityWide.id,
|
||
partnerAccountId: cityWide.partnerAccountId,
|
||
scopeType: cityWide.scopeType,
|
||
orderCommissionRate: cityWide.orderCommissionRate,
|
||
redeemCommissionRate: cityWide.redeemCommissionRate,
|
||
};
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
export function validatePartnerCityBinding(
|
||
existing: PartnerCityBindingInput[],
|
||
input: PartnerCityBindingInput,
|
||
excludeId?: string,
|
||
): PartnerCityValidationResult {
|
||
const others = existing.filter((b) => b.id !== excludeId);
|
||
|
||
if (others.some((b) => b.partnerAccountId === input.partnerAccountId)) {
|
||
return { ok: false, message: '该合伙人已绑定此城市' };
|
||
}
|
||
|
||
if (input.scopeType === 'CITY_WIDE') {
|
||
if (others.some((b) => b.scopeType === 'CITY_WIDE')) {
|
||
return { ok: false, message: '每城最多 1 名全城合伙人' };
|
||
}
|
||
return { ok: true };
|
||
}
|
||
|
||
const districts = normalizeDistrictCodes(input.districtCodes);
|
||
if (!districts.length) {
|
||
return { ok: false, message: '区域合伙人须至少选择一个区县' };
|
||
}
|
||
|
||
const occupied = new Set<string>();
|
||
for (const row of others) {
|
||
if (row.scopeType !== 'DISTRICT') continue;
|
||
for (const code of normalizeDistrictCodes(row.districtCodes)) {
|
||
occupied.add(code);
|
||
}
|
||
}
|
||
|
||
for (const code of districts) {
|
||
if (occupied.has(code)) {
|
||
return { ok: false, message: `区县 ${code} 已被其他区域合伙人占用` };
|
||
}
|
||
}
|
||
|
||
return { ok: true };
|
||
}
|
||
|
||
/** @deprecated use validatePartnerCityBinding */
|
||
export const validateCityPartnerBinding = validatePartnerCityBinding;
|
||
|
||
export const DEFAULT_MAX_PARTNER_COMMISSION_RATE = 0.05;
|
||
|
||
export function resolveMaxPartnerCommissionRate(maxRate?: number | null): number {
|
||
if (maxRate == null || Number.isNaN(Number(maxRate))) {
|
||
return DEFAULT_MAX_PARTNER_COMMISSION_RATE;
|
||
}
|
||
return Number(maxRate);
|
||
}
|
||
|
||
/** 订单佣金 + 核销佣金不得超过城市配置上限(默认 5%) */
|
||
export function validatePartnerCommissionRates(
|
||
orderCommissionRate: number,
|
||
redeemCommissionRate: number,
|
||
maxSumRate = DEFAULT_MAX_PARTNER_COMMISSION_RATE,
|
||
): PartnerCityValidationResult {
|
||
const sum = orderCommissionRate + redeemCommissionRate;
|
||
const max = resolveMaxPartnerCommissionRate(maxSumRate);
|
||
if (sum > max + 1e-9) {
|
||
return {
|
||
ok: false,
|
||
message: `订单佣金与核销佣金合计不得超过 ${(max * 100).toFixed(2)}%(当前 ${(sum * 100).toFixed(2)}%)`,
|
||
};
|
||
}
|
||
return { ok: true };
|
||
}
|