城市合伙人县区不做限制
This commit is contained in:
@@ -26,7 +26,7 @@ import {
|
|||||||
type PartnerPermissionKey,
|
type PartnerPermissionKey,
|
||||||
} from '@dukang/shared-types';
|
} from '@dukang/shared-types';
|
||||||
import { request, type Paginated } from '../lib/api';
|
import { request, type Paginated } from '../lib/api';
|
||||||
import { districtCodeLabel } from '../lib/china-region';
|
import { districtCodeLabel, formatDistrictLabels } from '../lib/china-region';
|
||||||
import { fmtTime } from '../lib/constants';
|
import { fmtTime } from '../lib/constants';
|
||||||
import CityDistrictMultiSelect from './CityDistrictMultiSelect';
|
import CityDistrictMultiSelect from './CityDistrictMultiSelect';
|
||||||
import PartnerSubAccountList from './PartnerSubAccountList';
|
import PartnerSubAccountList from './PartnerSubAccountList';
|
||||||
@@ -37,6 +37,7 @@ type PartnerRow = {
|
|||||||
phone: string;
|
phone: string;
|
||||||
name: string;
|
name: string;
|
||||||
scopeType?: string;
|
scopeType?: string;
|
||||||
|
districtCodes?: string[] | null;
|
||||||
orderCommissionRate?: number;
|
orderCommissionRate?: number;
|
||||||
redeemCommissionRate?: number;
|
redeemCommissionRate?: number;
|
||||||
accountCount: number;
|
accountCount: number;
|
||||||
@@ -211,6 +212,14 @@ export default function CityPartnersPanel({
|
|||||||
|
|
||||||
const columns: ColumnsType<PartnerRow> = [
|
const columns: ColumnsType<PartnerRow> = [
|
||||||
{ title: '公司名', dataIndex: 'companyName', ellipsis: true },
|
{ title: '公司名', dataIndex: 'companyName', ellipsis: true },
|
||||||
|
{
|
||||||
|
title: '区县',
|
||||||
|
dataIndex: 'districtCodes',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (codes: string[] | null | undefined, row) =>
|
||||||
|
row.scopeType === CityPartnerScopeType.CITY_WIDE ? '全城' : formatDistrictLabels(codes),
|
||||||
|
},
|
||||||
{ title: '主账号', dataIndex: 'phone', width: 120 },
|
{ title: '主账号', dataIndex: 'phone', width: 120 },
|
||||||
{
|
{
|
||||||
title: '管辖',
|
title: '管辖',
|
||||||
@@ -287,7 +296,7 @@ export default function CityPartnersPanel({
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="districtCodes"
|
name="districtCodes"
|
||||||
label="区县"
|
label="区县"
|
||||||
extra="仅可选当前开城城市下的区县;不可与其他区域合伙人重合"
|
extra="仅作标识,可多选当前城市下的区县(不做互斥)"
|
||||||
>
|
>
|
||||||
<CityDistrictMultiSelect cityCode={cityCode} />
|
<CityDistrictMultiSelect cityCode={cityCode} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -386,7 +395,7 @@ export default function CityPartnersPanel({
|
|||||||
name="districtCodes"
|
name="districtCodes"
|
||||||
label="区县"
|
label="区县"
|
||||||
rules={[{ required: true, message: '请选择至少一个区县' }]}
|
rules={[{ required: true, message: '请选择至少一个区县' }]}
|
||||||
extra="仅可选当前开城城市下的区县;不可与其他区域合伙人重合"
|
extra="仅作标识,可多选当前城市下的区县(不做互斥)"
|
||||||
>
|
>
|
||||||
<CityDistrictMultiSelect cityCode={cityCode} />
|
<CityDistrictMultiSelect cityCode={cityCode} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ export function districtCodeLabel(code: string): string {
|
|||||||
return codeToText[code] || code;
|
return codeToText[code] || code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 列表展示:区县码 → 中文名,顿号拼接 */
|
||||||
|
export function formatDistrictLabels(codes?: string[] | null): string {
|
||||||
|
if (!codes?.length) return '—';
|
||||||
|
return codes.map((c) => districtCodeLabel(String(c))).join('、');
|
||||||
|
}
|
||||||
|
|
||||||
export function parseRegionCodes(codes?: string[]): ParsedChinaRegion | null {
|
export function parseRegionCodes(codes?: string[]): ParsedChinaRegion | null {
|
||||||
if (!codes || codes.length < 3) return null;
|
if (!codes || codes.length < 3) return null;
|
||||||
const [provinceCode, cityCode, districtCode] = codes;
|
const [provinceCode, cityCode, districtCode] = codes;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
type PartnerPermissionKey,
|
type PartnerPermissionKey,
|
||||||
} from '@dukang/shared-types';
|
} from '@dukang/shared-types';
|
||||||
import { request, type Paginated } from '../lib/api';
|
import { request, type Paginated } from '../lib/api';
|
||||||
import { districtCodeLabel } from '../lib/china-region';
|
import { districtCodeLabel, formatDistrictLabels } from '../lib/china-region';
|
||||||
import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
|
import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
|
||||||
import { useAdminList } from '../lib/useAdminList';
|
import { useAdminList } from '../lib/useAdminList';
|
||||||
import CityDistrictMultiSelect from '../components/CityDistrictMultiSelect';
|
import CityDistrictMultiSelect from '../components/CityDistrictMultiSelect';
|
||||||
@@ -48,6 +48,7 @@ type Row = {
|
|||||||
cityId?: string | null;
|
cityId?: string | null;
|
||||||
cityName?: string | null;
|
cityName?: string | null;
|
||||||
scopeType?: string;
|
scopeType?: string;
|
||||||
|
districtCodes?: string[] | null;
|
||||||
orderCommissionRate?: number;
|
orderCommissionRate?: number;
|
||||||
redeemCommissionRate?: number;
|
redeemCommissionRate?: number;
|
||||||
bindingStatus?: string;
|
bindingStatus?: string;
|
||||||
@@ -259,7 +260,15 @@ export default function CityPartnersPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const columns: ColumnsType<Row> = [
|
const columns: ColumnsType<Row> = [
|
||||||
{ title: '公司名', dataIndex: 'companyName', ellipsis: true },
|
{ title: '公司名', dataIndex: 'companyName', ellipsis: true, width: 140 },
|
||||||
|
{
|
||||||
|
title: '区县',
|
||||||
|
dataIndex: 'districtCodes',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (codes: string[] | null | undefined, row) =>
|
||||||
|
row.scopeType === CityPartnerScopeType.CITY_WIDE ? '全城' : formatDistrictLabels(codes),
|
||||||
|
},
|
||||||
{ title: '城市', dataIndex: 'cityName', width: 90 },
|
{ title: '城市', dataIndex: 'cityName', width: 90 },
|
||||||
{ title: '主账号姓名', dataIndex: 'name', width: 100, ellipsis: true },
|
{ title: '主账号姓名', dataIndex: 'name', width: 100, ellipsis: true },
|
||||||
{ title: '登录手机', dataIndex: 'phone', width: 120 },
|
{ title: '登录手机', dataIndex: 'phone', width: 120 },
|
||||||
@@ -476,7 +485,7 @@ export default function CityPartnersPage() {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="districtCodes"
|
name="districtCodes"
|
||||||
label="区县"
|
label="区县"
|
||||||
extra="仅可选当前开城城市下的区县;不可与其他区域合伙人重合"
|
extra="仅作标识,可多选当前城市下的区县(不做互斥)"
|
||||||
>
|
>
|
||||||
<CityDistrictMultiSelect cityCode={editCityCode} />
|
<CityDistrictMultiSelect cityCode={editCityCode} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -591,7 +600,7 @@ export default function CityPartnersPage() {
|
|||||||
name="districtCodes"
|
name="districtCodes"
|
||||||
label="区县"
|
label="区县"
|
||||||
rules={[{ required: true, message: '请选择至少一个区县' }]}
|
rules={[{ required: true, message: '请选择至少一个区县' }]}
|
||||||
extra="仅可选当前开城城市下的区县;不可与其他区域合伙人重合"
|
extra="仅作标识,可多选当前城市下的区县(不做互斥)"
|
||||||
>
|
>
|
||||||
<CityDistrictMultiSelect cityCode={createCityCode} />
|
<CityDistrictMultiSelect cityCode={createCityCode} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
type PartnerPermissionKey,
|
type PartnerPermissionKey,
|
||||||
} from '@dukang/shared-types';
|
} from '@dukang/shared-types';
|
||||||
import { request, type Paginated } from '../lib/api';
|
import { request, type Paginated } from '../lib/api';
|
||||||
import { districtCodeLabel } from '../lib/china-region';
|
import { districtCodeLabel, formatDistrictLabels } from '../lib/china-region';
|
||||||
import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
|
import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
|
||||||
import { useAdminList } from '../lib/useAdminList';
|
import { useAdminList } from '../lib/useAdminList';
|
||||||
import CityDistrictMultiSelect from '../components/CityDistrictMultiSelect';
|
import CityDistrictMultiSelect from '../components/CityDistrictMultiSelect';
|
||||||
@@ -37,6 +37,7 @@ type Row = {
|
|||||||
cityId?: string | null;
|
cityId?: string | null;
|
||||||
cityName?: string | null;
|
cityName?: string | null;
|
||||||
scopeType?: string;
|
scopeType?: string;
|
||||||
|
districtCodes?: string[] | null;
|
||||||
orderCommissionRate?: number;
|
orderCommissionRate?: number;
|
||||||
redeemCommissionRate?: number;
|
redeemCommissionRate?: number;
|
||||||
storeCount: number;
|
storeCount: number;
|
||||||
@@ -174,6 +175,14 @@ export default function PartnersPage() {
|
|||||||
|
|
||||||
const columns: ColumnsType<Row> = [
|
const columns: ColumnsType<Row> = [
|
||||||
{ title: '公司名', dataIndex: 'companyName', ellipsis: true },
|
{ title: '公司名', dataIndex: 'companyName', ellipsis: true },
|
||||||
|
{
|
||||||
|
title: '区县',
|
||||||
|
dataIndex: 'districtCodes',
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (codes: string[] | null | undefined, row) =>
|
||||||
|
row.scopeType === CityPartnerScopeType.CITY_WIDE ? '全城' : formatDistrictLabels(codes),
|
||||||
|
},
|
||||||
{ title: '城市', dataIndex: 'cityName', width: 100 },
|
{ title: '城市', dataIndex: 'cityName', width: 100 },
|
||||||
{ title: '主账号', dataIndex: 'phone', width: 130 },
|
{ title: '主账号', dataIndex: 'phone', width: 130 },
|
||||||
{
|
{
|
||||||
@@ -251,7 +260,7 @@ export default function PartnersPage() {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="districtCodes"
|
name="districtCodes"
|
||||||
label="区县"
|
label="区县"
|
||||||
extra="仅可选当前开城城市下的区县;不可与其他区域合伙人重合"
|
extra="仅作标识,可多选当前城市下的区县(不做互斥)"
|
||||||
>
|
>
|
||||||
<CityDistrictMultiSelect cityCode={editCityCode} />
|
<CityDistrictMultiSelect cityCode={editCityCode} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -351,7 +360,7 @@ export default function PartnersPage() {
|
|||||||
name="districtCodes"
|
name="districtCodes"
|
||||||
label="区县"
|
label="区县"
|
||||||
rules={[{ required: true, message: '请选择至少一个区县' }]}
|
rules={[{ required: true, message: '请选择至少一个区县' }]}
|
||||||
extra="仅可选当前开城城市下的区县;不可与其他区域合伙人重合"
|
extra="仅作标识,可多选当前城市下的区县(不做互斥)"
|
||||||
>
|
>
|
||||||
<CityDistrictMultiSelect cityCode={createCityCode} />
|
<CityDistrictMultiSelect cityCode={createCityCode} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe('validatePartnerCityBinding', () => {
|
|||||||
expect(result.ok).toBe(false);
|
expect(result.ok).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects overlapping district codes', () => {
|
it('allows overlapping district codes as labels', () => {
|
||||||
const result = validatePartnerCityBinding(
|
const result = validatePartnerCityBinding(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -74,10 +74,7 @@ describe('validatePartnerCityBinding', () => {
|
|||||||
],
|
],
|
||||||
{ partnerAccountId: '11', scopeType: 'DISTRICT', districtCodes: ['410105'] },
|
{ partnerAccountId: '11', scopeType: 'DISTRICT', districtCodes: ['410105'] },
|
||||||
);
|
);
|
||||||
expect(result.ok).toBe(false);
|
expect(result.ok).toBe(true);
|
||||||
expect(result.message).toContain('区域重合');
|
|
||||||
expect(result.message).toContain('甲公司');
|
|
||||||
expect(result.occupiedCodes).toEqual(['410105']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows valid district binding', () => {
|
it('allows valid district binding', () => {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export interface PartnerCityResolveRef {
|
|||||||
export interface PartnerCityValidationResult {
|
export interface PartnerCityValidationResult {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
message?: string;
|
message?: string;
|
||||||
occupiedCodes?: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeDistrictCodes(codes?: string[] | null): string[] {
|
function normalizeDistrictCodes(codes?: string[] | null): string[] {
|
||||||
@@ -101,27 +100,7 @@ export function validatePartnerCityBinding(
|
|||||||
return { ok: false, message: '区域合伙人须至少选择一个区县' };
|
return { ok: false, message: '区域合伙人须至少选择一个区县' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const occupiedBy = new Map<string, string>();
|
// 区县仅为标识,允许多个区域合伙人选择相同区县
|
||||||
for (const row of others) {
|
|
||||||
if (row.scopeType !== 'DISTRICT') continue;
|
|
||||||
const owner = row.companyName?.trim() || '其他区域合伙人';
|
|
||||||
for (const code of normalizeDistrictCodes(row.districtCodes)) {
|
|
||||||
if (!occupiedBy.has(code)) occupiedBy.set(code, owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const overlaps = districts.filter((code) => occupiedBy.has(code));
|
|
||||||
if (overlaps.length) {
|
|
||||||
const detail = overlaps
|
|
||||||
.map((code) => `${code}(${occupiedBy.get(code)})`)
|
|
||||||
.join('、');
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
message: `区域重合:以下区县已被占用,请改选其他区县 — ${detail}`,
|
|
||||||
occupiedCodes: overlaps,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export class AdminPartnersService {
|
|||||||
maxPartnerCommissionRate:
|
maxPartnerCommissionRate:
|
||||||
p.city?.maxPartnerCommissionRate != null ? Number(p.city.maxPartnerCommissionRate) : null,
|
p.city?.maxPartnerCommissionRate != null ? Number(p.city.maxPartnerCommissionRate) : null,
|
||||||
scopeType: p.scopeType,
|
scopeType: p.scopeType,
|
||||||
|
districtCodes: this.partnerCityService.parseDistrictCodes(p.districtCodes),
|
||||||
orderCommissionRate: Number(p.orderCommissionRate ?? 0),
|
orderCommissionRate: Number(p.orderCommissionRate ?? 0),
|
||||||
redeemCommissionRate: Number(p.redeemCommissionRate ?? 0.03),
|
redeemCommissionRate: Number(p.redeemCommissionRate ?? 0.03),
|
||||||
bindingStatus: p.bindingStatus,
|
bindingStatus: p.bindingStatus,
|
||||||
|
|||||||
Reference in New Issue
Block a user