b920c894b9
CI / verify (pull_request) Has been cancelled
Allow deleting open cities after listing partners/stores; company name, address, and districts optional on partner create. Co-authored-by: Cursor <cursoragent@cursor.com>
130 lines
4.0 KiB
TypeScript
130 lines
4.0 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;
|
||
/** 仅用于重合报错文案 */
|
||
companyName?: string | null;
|
||
}
|
||
|
||
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 };
|
||
}
|
||
|
||
// 区域合伙人所选区县可为空(录入时可稍后补全)
|
||
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 };
|
||
}
|