تفکیک عمده / تک
@@ -295,10 +295,14 @@ import {
} from '@ant-design/icons-vue';
import { jdate, price } from '../utils/filters';
import { useLayoutStore } from '../stores/layout';
+import { useUsersStore } from '../stores/users';
import api from '../api/client';
const loading = ref(false);
const layout = useLayoutStore();
+// Retail («تک فروشی») figures are for the super admin only; the API also omits them.
+const users = useUsersStore();
+const isSuper = computed(() => users.isSuperAdmin);
const chartHeight = computed(() => (layout.isMobile ? 240 : 300));
const donutHeight = computed(() => (layout.isMobile ? 220 : 240));
@@ -419,14 +423,13 @@ const wholesaleTotal = computed(() =>
);
const trendSeries = computed(() => {
const trend: OrdersTrendPoint[] = data.ordersTrend ?? [];
- return [
- { name: 'تک فروشی', data: trend.map((d) => d.retail) },
- { name: 'عمده فروشی', data: trend.map((d) => d.wholesale) },
- ];
+ const wholesale = { name: 'عمده فروشی', data: trend.map((d) => d.wholesale) };
+ if (!isSuper.value) return [wholesale];
+ return [{ name: 'تک فروشی', data: trend.map((d) => d.retail) }, wholesale];
});
const trendChartOptions = computed(() => ({
chart: { ...BASE_CHART, stacked: trendType.value === 'bar' },
- colors: [COLOR_PRIMARY, COLOR_SUCCESS],
+ colors: isSuper.value ? [COLOR_PRIMARY, COLOR_SUCCESS] : [COLOR_SUCCESS],
dataLabels: { enabled: false },
stroke: trendType.value === 'area' ? { curve: 'smooth', width: 2.5 } : { width: 0 },
fill:
diff --git a/apps/api/src/modules/admin-dashboard/admin-dashboard.controller.ts b/apps/api/src/modules/admin-dashboard/admin-dashboard.controller.ts
index d5068cd..0b222ce 100644
--- a/apps/api/src/modules/admin-dashboard/admin-dashboard.controller.ts
+++ b/apps/api/src/modules/admin-dashboard/admin-dashboard.controller.ts
@@ -1,6 +1,8 @@
import { Controller, Get, Query } from '@nestjs/common';
import { AdminDashboardService } from './admin-dashboard.service';
-import { RequirePermission } from '../auth/guards';
+import { CurrentAdmin, RequirePermission } from '../auth/guards';
+import type { AuthenticatedAdmin } from '../auth/jwt.strategy';
+import { isSuperAdmin } from '../auth/super-admin';
/**
* Legacy shipped two dashboards behind two rids:
@@ -18,8 +20,8 @@ export class AdminDashboardController {
@Get('overview')
@RequirePermission('dashboard.view')
- overview() {
- return this.dashboard.overview();
+ overview(@CurrentAdmin() admin: AuthenticatedAdmin) {
+ return this.dashboard.overview(isSuperAdmin(admin.mobile));
}
/** @deprecated superseded by GET overview; kept as an alias. */
diff --git a/apps/api/src/modules/admin-dashboard/admin-dashboard.service.ts b/apps/api/src/modules/admin-dashboard/admin-dashboard.service.ts
index 6cc96d7..29eda8b 100644
--- a/apps/api/src/modules/admin-dashboard/admin-dashboard.service.ts
+++ b/apps/api/src/modules/admin-dashboard/admin-dashboard.service.ts
@@ -37,8 +37,12 @@ export class AdminDashboardService {
status: { in: [OrderStatus.DELIVERED_TO_AGENT, OrderStatus.DELIVERED_TO_CUSTOMER] },
};
- /** The single merged dashboard endpoint (`GET admin/dashboard/overview`). */
- async overview() {
+ /**
+ * The single merged dashboard endpoint (`GET admin/dashboard/overview`).
+ * Retail («تک فروشی») figures are only built for the super admin; for anyone
+ * else they never leave the server.
+ */
+ async overview(viewerIsSuperAdmin = false) {
const since30 = this.daysAgo(30);
const since7 = this.daysAgo(7);
@@ -111,7 +115,11 @@ export class AdminDashboardService {
if (o.channel === 'WHOLESALE') bucket.wholesale += 1;
else bucket.retail += 1;
}
- const ordersTrend = [...trendByDay.entries()].map(([date, v]) => ({ date, ...v }));
+ // Non-super admins get wholesale only: their `total` is the wholesale count
+ // and there is no `retail` field, so the retail number cannot be derived.
+ const ordersTrend = [...trendByDay.entries()].map(([date, v]) =>
+ viewerIsSuperAdmin ? { date, ...v } : { date, total: v.wholesale, wholesale: v.wholesale },
+ );
const statusLabels: Record = {
SUSPENDED: 'معلق (سقف اعتبار)',
@@ -130,12 +138,14 @@ export class AdminDashboardService {
}));
const channelLabels: Record = { RETAIL: 'تک فروشی', WHOLESALE: 'عمده فروشی' };
- const channelBreakdown = channelGroups.map((g) => ({
- channel: g.channel,
- label: channelLabels[g.channel] ?? g.channel,
- count: g._count,
- grams: g._sum.totalGoldWeightGrams ?? new Prisma.Decimal(0),
- }));
+ const channelBreakdown = viewerIsSuperAdmin
+ ? channelGroups.map((g) => ({
+ channel: g.channel,
+ label: channelLabels[g.channel] ?? g.channel,
+ count: g._count,
+ grams: g._sum.totalGoldWeightGrams ?? new Prisma.Decimal(0),
+ }))
+ : [];
const productTotals = new Map();
for (const it of topProductRows) {