feat(assoc): v4.0.1 合伙人关联码、分佣账单与 H5 用户管理

订单佣金只认关联用户;合伙人备注写入独立表;H5 增加用户管理与首页统计。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 14:35:39 +08:00
parent 3b669f7e38
commit 9c8d5f2cad
125 changed files with 6355 additions and 1436 deletions
-40
View File
@@ -1,49 +1,9 @@
import { describe, expect, it } from 'vitest';
import {
resolveOrderCityPartner,
validatePartnerCityBinding,
validatePartnerCommissionRates,
} from './city-partner';
describe('resolveOrderCityPartner', () => {
const bindings = [
{
id: '1',
partnerAccountId: '10',
scopeType: 'CITY_WIDE' as const,
districtCodes: null,
orderCommissionRate: 0,
redeemCommissionRate: 0.03,
bindingStatus: 'ACTIVE' as const,
},
{
id: '2',
partnerAccountId: '20',
scopeType: 'DISTRICT' as const,
districtCodes: ['410105', '金水区'],
orderCommissionRate: 0.01,
redeemCommissionRate: 0.03,
bindingStatus: 'ACTIVE' as const,
},
];
it('prefers district partner when district matches adcode', () => {
const ref = resolveOrderCityPartner(bindings, '410105');
expect(ref?.partnerAccountId).toBe('20');
expect(ref?.scopeType).toBe('DISTRICT');
});
it('falls back to city-wide partner', () => {
const ref = resolveOrderCityPartner(bindings, '410102');
expect(ref?.partnerAccountId).toBe('10');
expect(ref?.scopeType).toBe('CITY_WIDE');
});
it('returns null when no bindings', () => {
expect(resolveOrderCityPartner([], '410105')).toBeNull();
});
});
describe('validatePartnerCityBinding', () => {
it('rejects duplicate partner', () => {
const result = validatePartnerCityBinding(
-61
View File
@@ -11,72 +11,11 @@ export interface PartnerCityBindingInput {
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,
+6
View File
@@ -59,6 +59,12 @@ export const MINI_USER_STATIC_OSS_BASE =
/** 「我的」页资质公示长图(默认;系统设置 QUALIFICATION_DISCLOSURE_URL 可覆盖) */
export const QUALIFICATION_DISCLOSURE_URL = `${MINI_USER_STATIC_OSS_BASE}qualification-disclosure.png`;
/** 小程序开场金龙 GIF(冷启动;不进主包,走 OSS) */
export const JIUZU_SPLASH_GIF_URL = `${MINI_USER_STATIC_OSS_BASE}jiuzu-dragon.gif`;
/** 小程序开场酒祖印记图(冷启动;不进主包,走 OSS) */
export const JIUZU_SPLASH_MARK_URL = `${MINI_USER_STATIC_OSS_BASE}jiuzu-dragon-mark.png`;
/** 总部客服电话(默认;系统设置 CUSTOMER_SERVICE_PHONE 可覆盖) */
export const CUSTOMER_SERVICE_PHONE = '13203801799';
+2
View File
@@ -23,6 +23,7 @@ export enum UserSourceType {
FRIEND_REFERRAL = 'FRIEND_REFERRAL',
OFFLINE_EVENT = 'OFFLINE_EVENT',
PARTNER_PROXY = 'PARTNER_PROXY',
PARTNER_ASSOC = 'PARTNER_ASSOC',
OTHER = 'OTHER',
}
@@ -33,6 +34,7 @@ export const USER_SOURCE_TYPE_LABELS: Record<UserSourceType, string> = {
[UserSourceType.FRIEND_REFERRAL]: '好友推荐',
[UserSourceType.OFFLINE_EVENT]: '线下活动',
[UserSourceType.PARTNER_PROXY]: '代下单',
[UserSourceType.PARTNER_ASSOC]: '合伙人关联',
[UserSourceType.OTHER]: '其他',
};
@@ -29,6 +29,7 @@ export const HQ_PERMISSION_CATALOG = [
{ key: 'resources', label: 'OSS 资源库', group: '业务' },
{ key: 'logs', label: '日志', group: '业务' },
{ key: 'users_delete', label: '删除用户', group: '危险操作' },
{ key: 'users_partner_assoc', label: '修改用户关联合伙人', group: '危险操作' },
{ key: 'orders_delete', label: '删除订单', group: '危险操作' },
{ key: 'cities_delete', label: '删除城市', group: '危险操作' },
{ key: 'store_categories_delete', label: '删除门店分类', group: '危险操作' },
@@ -70,6 +71,7 @@ export const HQ_DEBUG_PERMISSION_KEYS = [
export const HQ_DANGEROUS_PERMISSION_KEYS = [
'users_delete',
'users_partner_assoc',
'orders_delete',
'cities_delete',
'store_categories_delete',
+1
View File
@@ -21,6 +21,7 @@ export * from './promo';
export * from './hq-permissions';
export * from './hq-list-columns';
export * from './partner';
export * from './partner-assoc';
export * from './shop';
export * from './city-partner';
export * from './city-warehouse';
@@ -0,0 +1,86 @@
export type PartnerAssocBindRequest = {
scene?: string;
partnerId?: string;
};
export type PartnerAssocBindResult = {
bound: boolean;
alreadyBound: boolean;
partnerId: string;
partnerName: string;
};
export type PartnerAssocSummary = {
partnerId: string;
qrcodeUrl: string | null;
userCount: number;
companyName?: string | null;
name: string;
};
export type PartnerAssocStats = {
userTotal: number;
userToday: number;
userMonth: number;
orderTotal: number;
orderToday: number;
orderMonth: number;
};
export type PartnerAssocUserSort = 'createdAt' | 'boundAt' | 'orderCount';
export type PartnerAssocUserItem = {
id: string;
userNo: string;
nickname: string | null;
/** 仅 HQ 列表返回;合伙人 H5 不返回 */
hqRemark?: string | null;
/** 仅当前主合伙人自己的备注 */
partnerRemark?: string | null;
phone: string | null;
createdAt?: string;
boundAt: string;
orderCount: number;
};
export type PartnerAssocUserOrderItem = {
id: string;
orderNo: string;
productName: string;
quantity: number;
payAmount: number;
paidAt: string | null;
status: string;
};
export type PartnerAssocRemarkRequest = {
remark?: string | null;
};
export type PartnerCommissionOrderItem = {
id: string;
orderNo: string;
productName: string;
quantity: number;
payAmount: number;
rate: number | null;
commission: number;
paidAt: string | null;
userPhone?: string | null;
};
export type PartnerHqAssocUserItem = PartnerAssocUserItem & {
phone: string | null;
};
export type HqAssocPartnerBrief = {
id: string;
name: string;
companyName?: string | null;
phone?: string | null;
orderCommissionRate?: number | null;
};
export type HqSetUserAssocRequest = {
partnerAccountId?: string | null;
};
+23
View File
@@ -79,6 +79,29 @@ export interface RedeemRecordDto {
createdAt: string;
}
export const STORE_RATING_QUICK_TAGS = ['菜品好', '环境佳', '服务周到', '核销顺畅', '适合宴请'] as const;
export const STORE_RATING_MAX_IMAGES = 6;
export const STORE_RATING_MAX_COMMENT = 200;
export type StoreRatingDto = {
serviceScore: number;
envScore: number;
comment?: string | null;
tags?: string[];
imageUrls?: string[];
createdAt?: string;
};
export type SubmitStoreRatingRequest = {
redeemRecordId: string;
serviceScore: number;
envScore: number;
comment?: string;
tags?: string[];
imageUrls?: string[];
};
export interface RedeemChannelStatsBucket {
channel: RedeemChannel;
count: number;
+17
View File
@@ -138,8 +138,25 @@ export interface PartnerBillDto {
rejectReason?: string | null;
}
export type PartnerBillItemKind = 'ORDER' | 'REDEEM';
export interface PartnerBillItemDto {
id: string;
kind: PartnerBillItemKind;
refId: string;
refNo: string;
title?: string | null;
extra?: string | null;
baseAmount: number;
rate: number;
commission: number;
occurredAt: string;
}
export interface PartnerBillDetailDto extends PartnerBillDto {
partnerId: string;
orderItems?: PartnerBillItemDto[];
redeemItems?: PartnerBillItemDto[];
}
export interface StoreBillDto {
+12
View File
@@ -150,10 +150,18 @@ export type PartnerProxyOrderStoreOption = {
district?: string | null;
};
export type PartnerProxyOrderPartnerOption = {
id: string;
companyName?: string | null;
name: string;
phone?: string | null;
};
export type PartnerProxyOrderOptions = {
products: PartnerProxyOrderProductOption[];
promoCodes: PartnerProxyOrderPromoOption[];
stores: PartnerProxyOrderStoreOption[];
partners?: PartnerProxyOrderPartnerOption[];
};
export type PartnerProxyOrderPreviewRequest = {
@@ -194,6 +202,8 @@ export type PartnerProxyOrderCreateRequest = {
quantity: number;
promoCodeId?: string;
skuId?: string;
/** 代下单显式关联合伙人;不传则本单不计订单佣金 */
assocPartnerAccountId?: string;
};
/** 合伙人代下单列表项(与 OrderDto 兼容,附带收货信息) */
@@ -229,4 +239,6 @@ export type HqProxyOrderCreateRequest = {
quantity: number;
promoCodeId?: string;
skuId?: string;
/** 代下单显式关联合伙人;不传则本单不计订单佣金 */
assocPartnerAccountId?: string;
};