diff --git a/apps/h5-partner/src/PartnerAppRoutes.tsx b/apps/h5-partner/src/PartnerAppRoutes.tsx index 3c0f461..f7edfc6 100644 --- a/apps/h5-partner/src/PartnerAppRoutes.tsx +++ b/apps/h5-partner/src/PartnerAppRoutes.tsx @@ -13,7 +13,6 @@ import CenterPage from './pages/CenterPage'; import PartnerMePage from './pages/PartnerMePage'; import BillsPage from './pages/BillsPage'; import SettlementPage from './pages/SettlementPage'; -import ReshipPage from './pages/ReshipPage'; import WeeklyReportPage from './pages/WeeklyReportPage'; import ProxyOrderPage from './pages/ProxyOrderPage'; import StaffListPage from './pages/StaffListPage'; @@ -34,7 +33,6 @@ function PrimaryRoutes() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/apps/h5-partner/src/contexts/PartnerSessionContext.tsx b/apps/h5-partner/src/contexts/PartnerSessionContext.tsx index 3478f57..197c14f 100644 --- a/apps/h5-partner/src/contexts/PartnerSessionContext.tsx +++ b/apps/h5-partner/src/contexts/PartnerSessionContext.tsx @@ -55,6 +55,8 @@ function accountFromProfile(profile: PartnerSessionProfile): PartnerAccount { hasWechat: profile.hasWechat, wxNickname: profile.wxNickname, wxAvatarUrl: profile.wxAvatarUrl, + managedWarehouseId: profile.managedWarehouseId, + hasWarehouseAccess: profile.hasWarehouseAccess, }; } diff --git a/apps/h5-partner/src/lib/api.ts b/apps/h5-partner/src/lib/api.ts index f8258e7..2c7c8cb 100644 --- a/apps/h5-partner/src/lib/api.ts +++ b/apps/h5-partner/src/lib/api.ts @@ -36,6 +36,8 @@ export type PartnerSessionProfile = Pick< | 'hasWechat' | 'wxNickname' | 'wxAvatarUrl' + | 'managedWarehouseId' + | 'hasWarehouseAccess' > & { staffRole?: PartnerStaffRole; }; @@ -125,6 +127,8 @@ function profileFromMe(me: PartnerMe): PartnerSessionProfile { hasWechat: me.hasWechat, wxNickname: me.wxNickname, wxAvatarUrl: me.wxAvatarUrl, + managedWarehouseId: me.managedWarehouseId, + hasWarehouseAccess: me.hasWarehouseAccess, }; } diff --git a/apps/h5-partner/src/lib/partnerAccess.ts b/apps/h5-partner/src/lib/partnerAccess.ts index fd6c5cc..a8b32ca 100644 --- a/apps/h5-partner/src/lib/partnerAccess.ts +++ b/apps/h5-partner/src/lib/partnerAccess.ts @@ -35,6 +35,15 @@ export function getPartnerNavKind(account: PartnerMe | null | undefined): Partne return 'store_staff'; } +/** 是否已配置管仓:主账号看 hasWarehouseAccess;子账号还需有仓库相关权限 */ +export function hasWarehouseAccess(account: PartnerMe | null | undefined): boolean { + if (!account) return false; + if (account.hasWarehouseAccess === false) return false; + if (account.hasWarehouseAccess === true) return true; + // 旧缓存无字段时保守:主账号未知视为无,避免误展示全城订单 + return false; +} + export function partnerHomePath(account: PartnerMe | null | undefined): string { return '/'; } diff --git a/apps/h5-partner/src/pages/HomePage.tsx b/apps/h5-partner/src/pages/HomePage.tsx index 519d9b5..6e07efc 100644 --- a/apps/h5-partner/src/pages/HomePage.tsx +++ b/apps/h5-partner/src/pages/HomePage.tsx @@ -3,7 +3,7 @@ import { Link, useNavigate } from 'react-router-dom'; import type { PartnerLeaderboardEntry } from '@dukang/shared-types'; import { isLoggedIn, request } from '../lib/api'; import { fetchPartnerLeaderboard } from '../lib/leaderboard'; -import { getPartnerNavKind } from '../lib/partnerAccess'; +import { getPartnerNavKind, hasWarehouseAccess } from '../lib/partnerAccess'; import { usePartnerSession } from '../contexts/PartnerSessionContext'; function fmtMoney(n: number) { @@ -135,6 +135,7 @@ export default function HomePage() { const navKind = getPartnerNavKind(account); const isPrimary = navKind === 'primary'; const isWarehouse = navKind === 'warehouse_staff'; + const warehouseOk = hasWarehouseAccess(account); const [dash, setDash] = useState | null>(null); const [stores, setStores] = useState>>([]); @@ -147,10 +148,12 @@ export default function HomePage() { useEffect(() => { if (!isLoggedIn()) { navigate('/login'); return; } - if (isWarehouse || isPrimary) { + if ((isWarehouse || isPrimary) && warehouseOk) { request<{ list: Array> }>('PARTNER_H5', '/partner/orders') .then((data) => setOrders(Array.isArray(data.list) ? data.list : [])) .catch(() => setOrders([])); + } else { + setOrders([]); } if (!isWarehouse) { request>('PARTNER_H5', '/partner/dashboard').then(setDash).catch(() => {}); @@ -165,7 +168,7 @@ export default function HomePage() { } else { request>('PARTNER_H5', '/partner/dashboard').then(setDash).catch(() => {}); } - }, [navigate, isWarehouse, isPrimary]); + }, [navigate, isWarehouse, isPrimary, warehouseOk]); const storeCount = Number(dash?.storeCount || stores.length || 0); const orderStats = useMemo(() => summarizeOrders(orders), [orders]); @@ -190,7 +193,6 @@ export default function HomePage() { const orderCount = Number(dash?.orderCount || orderStats.todayCount || 0); const revenue = orderCount * 128.45; const profit = revenue * 0.25; - const pendingShipBadge = orderStats.pendingShip; return (
@@ -224,13 +226,6 @@ export default function HomePage() {
录入新店 - -
- assignment_return - {pendingShipBadge > 0 && {pendingShipBadge}} -
- 补发处理 -
account_balance @@ -252,7 +247,7 @@ export default function HomePage() {

订单管理

)} - {(isPrimary || isWarehouse) && ( + {(isPrimary || isWarehouse) && warehouseOk && ( )} + {(isPrimary || isWarehouse) && !warehouseOk && ( +
+
+ warehouse +

未配置仓库管理权限

+

+ 购酒订单由总部履约,不会推送到本账号。如需管仓发货,请联系总部配置仓库。 +

+
+
+ )} + {!isWarehouse && ( <>
diff --git a/apps/h5-partner/src/pages/OrderListPage.tsx b/apps/h5-partner/src/pages/OrderListPage.tsx index c9e683a..bd7ce97 100644 --- a/apps/h5-partner/src/pages/OrderListPage.tsx +++ b/apps/h5-partner/src/pages/OrderListPage.tsx @@ -2,6 +2,8 @@ import { useEffect, useMemo, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import PageHeader from '@dukang/shared-ui/PageHeader'; import { isLoggedIn, request } from '../lib/api'; +import { usePartnerSession } from '../contexts/PartnerSessionContext'; +import { hasWarehouseAccess } from '../lib/partnerAccess'; type DateFilter = 'today' | '7d' | '30d'; type StatusFilter = 'ALL' | 'PENDING_SHIP' | 'SHIPPING' | 'COMPLETED' | 'ABNORMAL'; @@ -34,15 +36,24 @@ type OrderListPageProps = { tabRoot?: boolean; }; +type OrdersResponse = { + list: Array>; + hasWarehouseAccess?: boolean; + message?: string; +}; + export default function OrderListPage({ tabRoot = false }: OrderListPageProps) { const navigate = useNavigate(); - const [data, setData] = useState<{ list: Array> }>({ list: [] }); + const { account } = usePartnerSession(); + const warehouseOk = hasWarehouseAccess(account); + const [data, setData] = useState({ list: [] }); const [tab, setTab] = useState<'orders' | 'coupons'>('orders'); const [dateFilter, setDateFilter] = useState('today'); const [statusFilter, setStatusFilter] = useState('ALL'); const [shippingId, setShippingId] = useState(null); const [shipForm, setShipForm] = useState({ logisticsCompany: '', trackingNo: '', manualQueryUrl: '' }); const [shipModalId, setShipModalId] = useState(null); + const [loaded, setLoaded] = useState(false); useEffect(() => { document.title = '订单管理'; @@ -50,8 +61,22 @@ export default function OrderListPage({ tabRoot = false }: OrderListPageProps) { useEffect(() => { if (!isLoggedIn()) { navigate('/login'); return; } - request<{ list: Array> }>('PARTNER_H5', '/partner/orders').then(setData); - }, [navigate]); + if (!warehouseOk) { + setData({ list: [], hasWarehouseAccess: false, message: '未配置仓库管理权限' }); + setLoaded(true); + return; + } + request('PARTNER_H5', '/partner/orders') + .then((res) => { + setData({ + list: Array.isArray(res.list) ? res.list : [], + hasWarehouseAccess: res.hasWarehouseAccess, + message: res.message, + }); + }) + .catch(() => setData({ list: [] })) + .finally(() => setLoaded(true)); + }, [navigate, warehouseOk]); const filtered = useMemo(() => data.list.filter((o) => { if (statusFilter === 'ALL') return true; @@ -83,8 +108,12 @@ export default function OrderListPage({ tabRoot = false }: OrderListPageProps) { body: JSON.stringify(shipForm), }); setShipModalId(null); - const next = await request<{ list: Array> }>('PARTNER_H5', '/partner/orders'); - setData(next); + const next = await request('PARTNER_H5', '/partner/orders'); + setData({ + list: Array.isArray(next.list) ? next.list : [], + hasWarehouseAccess: next.hasWarehouseAccess, + message: next.message, + }); } catch (err) { window.alert(err instanceof Error ? err.message : '发货失败'); } finally { @@ -97,6 +126,21 @@ export default function OrderListPage({ tabRoot = false }: OrderListPageProps) { return s.includes('PENDING') || s === 'PAID' || s === 'PENDING_SHIP'; } + if (loaded && !warehouseOk) { + return ( +
+ {!tabRoot && navigate('/')} />} +
+ warehouse +

未配置仓库管理权限

+

+ 购酒订单由总部履约,不会推送到本账号。如需管仓发货,请联系总部配置仓库。 +

+
+
+ ); + } + return (
{!tabRoot && navigate('/')} />} diff --git a/apps/h5-partner/src/styles.css b/apps/h5-partner/src/styles.css index de0138a..34d0b9c 100644 --- a/apps/h5-partner/src/styles.css +++ b/apps/h5-partner/src/styles.css @@ -3278,8 +3278,17 @@ body { border-top: 1px dashed rgba(166, 29, 36, 0.15); } -.partner-home--flush-top .partner-home-body { - padding-top: 20px; +.partner-warehouse-denied { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 20px 12px; +} + +.partner-warehouse-denied .material-symbols-outlined { + font-size: 36px; + font-variation-settings: 'FILL' 0; } .partner-home--flush-top.partner-store-page, diff --git a/packages/shared-types/src/partner.ts b/packages/shared-types/src/partner.ts index 30cc540..60a985e 100644 --- a/packages/shared-types/src/partner.ts +++ b/packages/shared-types/src/partner.ts @@ -17,6 +17,10 @@ export interface PartnerMe { /** 子账号联系主账号用 */ primaryPhone?: string; primaryName?: string; + /** 主账号绑定的管仓 ID(有则本城酒单推送至该仓) */ + managedWarehouseId?: string | null; + /** 是否已配置仓库管理(无则不展示/不推送酒订单) */ + hasWarehouseAccess?: boolean; } export interface UpdatePartnerMeRequest { diff --git a/server/dukang-api/src/modules/city-scope/partner-city.service.ts b/server/dukang-api/src/modules/city-scope/partner-city.service.ts index 461b6ca..69e94e4 100644 --- a/server/dukang-api/src/modules/city-scope/partner-city.service.ts +++ b/server/dukang-api/src/modules/city-scope/partner-city.service.ts @@ -103,10 +103,16 @@ export class PartnerCityService { if (!primary.cityId) return { id: -1n }; const warehouseIds = await this.resolveManagedWarehouseIds(primary.id); - if (warehouseIds.length > 0) { - return { fulfillmentWarehouseId: { in: warehouseIds } }; + // 未配置管仓:不推送/不展示酒订单(同城无仓由总部履约) + if (warehouseIds.length === 0) { + return { id: -1n }; } - return { cityId: primary.cityId }; + return { fulfillmentWarehouseId: { in: warehouseIds } }; + } + + async hasManagedWarehouse(partnerAccountId: bigint): Promise { + const ids = await this.resolveManagedWarehouseIds(partnerAccountId); + return ids.length > 0; } /** 合伙人可管仓库:主账号 managedWarehouseId + 绑定为管仓合伙人的仓 */ diff --git a/server/dukang-api/src/modules/settlement/settlement.controller.ts b/server/dukang-api/src/modules/settlement/settlement.controller.ts index 4117757..011d715 100644 --- a/server/dukang-api/src/modules/settlement/settlement.controller.ts +++ b/server/dukang-api/src/modules/settlement/settlement.controller.ts @@ -9,6 +9,7 @@ import { HqAuthGuard } from '../../common/guards/hq-auth.guard'; import { HqOperation } from '../../common/hq-operation/hq-operation.decorator'; import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants'; import { PrismaService } from '../../common/prisma/prisma.module'; +import { PartnerCityService } from '../city-scope/partner-city.service'; class UpdatePartnerMeDto { @IsOptional() @@ -243,7 +244,10 @@ export class AdminWineryBillController { @Controller('partner/me') @UseGuards(JwtAuthGuard) export class PartnerMeController { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly partnerCityService: PartnerCityService, + ) {} @Get() async me(@CurrentUser() user: AuthUser) { @@ -274,6 +278,7 @@ export class PartnerMeController { where: { id: account.parentAccountId }, }); } + const hasWarehouseAccess = await this.partnerCityService.hasManagedWarehouse(primary.id); return { id: account.id.toString(), name: account.name, @@ -288,6 +293,8 @@ export class PartnerMeController { hasWechat: !!account.wxOpenId, wxNickname: account.wxNickname ?? undefined, wxAvatarUrl: account.wxAvatarUrl ?? undefined, + managedWarehouseId: primary.managedWarehouseId?.toString() ?? null, + hasWarehouseAccess, }; } } diff --git a/server/dukang-api/src/modules/trade/trade.service.ts b/server/dukang-api/src/modules/trade/trade.service.ts index 31be90d..ffcce37 100644 --- a/server/dukang-api/src/modules/trade/trade.service.ts +++ b/server/dukang-api/src/modules/trade/trade.service.ts @@ -509,6 +509,7 @@ export class TradeService { async listPartnerOrders(partnerAccountId: bigint, page = 1, pageSize = 20) { const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId); + const hasWarehouseAccess = await this.partnerCityService.hasManagedWarehouse(primary.id); const where = await this.partnerCityService.buildPartnerOrderWhere(primary.id); const [list, total] = await Promise.all([ this.prisma.order.findMany({ @@ -520,7 +521,14 @@ export class TradeService { }), this.prisma.order.count({ where }), ]); - return { list: serializeBigInt(list.map(mapOrderCompat)), total, page, pageSize }; + return { + list: serializeBigInt(list.map(mapOrderCompat)), + total, + page, + pageSize, + hasWarehouseAccess, + message: hasWarehouseAccess ? undefined : '未配置仓库管理权限,购酒订单由总部履约', + }; } async getPartnerOrder(partnerAccountId: bigint, orderId: bigint) {