diff --git a/apps/api/release/app.js b/apps/api/release/app.js index db34b4b..b32b96c 100644 --- a/apps/api/release/app.js +++ b/apps/api/release/app.js @@ -84977,8 +84977,7 @@ var require_acl_constants = __commonJS({ "config.wallet", "custom.config.statics.slider", "custom.config.statics.tags.banner.ids", - "dashboard.omde", - "dashboard.tak", + "dashboard.view", "document.create", "document.list", "document.list.confirm", @@ -92609,6 +92608,112 @@ var require_admin_dashboard_service = __commonJS({ static SOLD = { status: { in: [client_1.OrderStatus.DELIVERED_TO_AGENT, client_1.OrderStatus.DELIVERED_TO_CUSTOMER] } }; + /** The single merged dashboard endpoint (`GET admin/dashboard/overview`). */ + async overview() { + const since30 = this.daysAgo(30); + const since7 = this.daysAgo(7); + const [omde, tak, ordersLast30, statusGroups, channelGroups, topProductRows, latestGoldQuote, debtAgg] = await Promise.all([ + this.omdeAnalytics(), + this.takAnalytics(), + // روند سفارش‌ها در ۳۰ روز گذشته — one row per order, bucketed by day below. + this.prisma.order.findMany({ + where: { createdAt: { gte: since30 } }, + select: { createdAt: true, channel: true } + }), + // دونات وضعیت سفارشات (۳۰ روز گذشته) + this.prisma.order.groupBy({ + by: ["status"], + where: { createdAt: { gte: since30 } }, + _count: true + }), + // تفکیک عمده/تک (۳۰ روز گذشته) + this.prisma.order.groupBy({ + by: ["channel"], + where: { createdAt: { gte: since30 } }, + _count: true, + _sum: { totalGoldWeightGrams: true } + }), + // محصولات پرفروش (۳۰ روز گذشته) — by shipped weight, top 6. + this.prisma.orderItem.findMany({ + where: { order: { createdAt: { gte: since30 } } }, + select: { + quantity: true, + computedWeightGrams: true, + productVariant: { select: { product: { select: { id: true, title: true } } } } + } + }), + // نرخ فعلی طلا — legacy's goldPrice widget (18 عیار / 750‰). + this.prisma.goldPriceQuote.findFirst({ + where: { karatPerMille: 750 }, + orderBy: { quotedAt: "desc" } + }), + // مجموع بدهی مشتریان (گرم طلا) — cached balance, see Customer.goldDebtGrams. + this.prisma.customer.aggregate({ _sum: { goldDebtGrams: true } }) + ]); + const trendByDay = /* @__PURE__ */ new Map(); + for (let i = 29; i >= 0; i--) { + trendByDay.set(this.dayKey(this.daysAgo(i)), { total: 0, retail: 0, wholesale: 0 }); + } + for (const o of ordersLast30) { + const key = this.dayKey(o.createdAt); + const bucket = trendByDay.get(key); + if (!bucket) + continue; + bucket.total += 1; + if (o.channel === "WHOLESALE") + bucket.wholesale += 1; + else + bucket.retail += 1; + } + const ordersTrend = [...trendByDay.entries()].map(([date, v]) => ({ date, ...v })); + const statusLabels = { + SUSPENDED: "\u0645\u0639\u0644\u0642 (\u0633\u0642\u0641 \u0627\u0639\u062A\u0628\u0627\u0631)", + PLACED: "\u062B\u0628\u062A \u0634\u062F\u0647", + ADMIN_APPROVED: "\u062A\u0627\u06CC\u06CC\u062F \u0645\u062F\u06CC\u0631", + SOURCING: "\u062F\u0631 \u062D\u0627\u0644 \u062A\u0627\u0645\u06CC\u0646", + PREPARING: "\u062F\u0631 \u062D\u0627\u0644 \u0622\u0645\u0627\u062F\u0647\u200C\u0633\u0627\u0632\u06CC", + DELIVERED_TO_AGENT: "\u062A\u062D\u0648\u06CC\u0644 \u0628\u0647 \u0646\u0645\u0627\u06CC\u0646\u062F\u0647", + DELIVERED_TO_CUSTOMER: "\u062A\u062D\u0648\u06CC\u0644 \u0628\u0647 \u0645\u0634\u062A\u0631\u06CC", + CANCELLED: "\u0644\u063A\u0648 \u0634\u062F\u0647" + }; + const statusBreakdown = statusGroups.map((g) => ({ + status: g.status, + label: statusLabels[g.status], + count: g._count + })); + const channelLabels = { RETAIL: "\u062A\u06A9 \u0641\u0631\u0648\u0634\u06CC", WHOLESALE: "\u0639\u0645\u062F\u0647 \u0641\u0631\u0648\u0634\u06CC" }; + const channelBreakdown = channelGroups.map((g) => ({ + channel: g.channel, + label: channelLabels[g.channel] ?? g.channel, + count: g._count, + grams: g._sum.totalGoldWeightGrams ?? new client_1.Prisma.Decimal(0) + })); + const productTotals = /* @__PURE__ */ new Map(); + for (const it of topProductRows) { + const p = it.productVariant.product; + const row = productTotals.get(p.id) ?? { title: p.title, weight: new client_1.Prisma.Decimal(0), qty: 0 }; + row.weight = row.weight.add(it.computedWeightGrams.mul(it.quantity)); + row.qty += it.quantity; + productTotals.set(p.id, row); + } + const topProducts = [...productTotals.values()].sort((a, b) => Number(b.weight) - Number(a.weight)).slice(0, 6); + return { + // ── everything the two legacy dashboards returned, unchanged ────── + ...omde, + takLastOrder: tak.lastOrder, + // ── new chart data ───────────────────────────────────────────── + ordersTrend, + statusBreakdown, + channelBreakdown, + topProducts, + goldPricePerGram: latestGoldQuote?.pricePerGramRial ?? null, + goldPriceQuotedAt: latestGoldQuote?.quotedAt ?? null, + totalCustomerDebtGrams: debtAgg._sum.goldDebtGrams ?? new client_1.Prisma.Decimal(0) + }; + } + dayKey(d) { + return d.toISOString().slice(0, 10); + } /** Legacy `GET get-analytics` — the wholesale dashboard. */ async omdeAnalytics() { const since30 = this.daysAgo(30); @@ -92837,9 +92942,14 @@ var require_admin_dashboard_controller = __commonJS({ constructor(dashboard) { this.dashboard = dashboard; } + overview() { + return this.dashboard.overview(); + } + /** @deprecated superseded by GET overview; kept as an alias. */ omde() { return this.dashboard.omdeAnalytics(); } + /** @deprecated superseded by GET overview; kept as an alias. */ tak() { return this.dashboard.takAnalytics(); } @@ -92853,16 +92963,23 @@ var require_admin_dashboard_controller = __commonJS({ } }; exports2.AdminDashboardController = AdminDashboardController; + __decorate4([ + (0, common_12.Get)("overview"), + (0, guards_1.RequirePermission)("dashboard.view"), + __metadata3("design:type", Function), + __metadata3("design:paramtypes", []), + __metadata3("design:returntype", void 0) + ], AdminDashboardController.prototype, "overview", null); __decorate4([ (0, common_12.Get)("omde"), - (0, guards_1.RequirePermission)("dashboard.omde"), + (0, guards_1.RequirePermission)("dashboard.view"), __metadata3("design:type", Function), __metadata3("design:paramtypes", []), __metadata3("design:returntype", void 0) ], AdminDashboardController.prototype, "omde", null); __decorate4([ (0, common_12.Get)("tak"), - (0, guards_1.RequirePermission)("dashboard.tak"), + (0, guards_1.RequirePermission)("dashboard.view"), __metadata3("design:type", Function), __metadata3("design:paramtypes", []), __metadata3("design:returntype", void 0) diff --git a/apps/api/release/seed.js b/apps/api/release/seed.js index ed4674c..3278e26 100644 --- a/apps/api/release/seed.js +++ b/apps/api/release/seed.js @@ -1620,8 +1620,7 @@ var ALL_RIDS = [ "config.wallet", "custom.config.statics.slider", "custom.config.statics.tags.banner.ids", - "dashboard.omde", - "dashboard.tak", + "dashboard.view", "document.create", "document.list", "document.list.confirm", @@ -1782,7 +1781,7 @@ async function main() { "persons.list.client.single.view", "persons.create", "product.list", - "dashboard.tak", + "dashboard.view", "comment" ] }, @@ -1800,7 +1799,7 @@ async function main() { "report.order.item", "report.product", "config.report", - "dashboard.omde" + "dashboard.view" ] }, { diff --git a/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp10336 b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp10336 new file mode 100644 index 0000000..d46657b Binary files /dev/null and b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp10336 differ diff --git a/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp25856 b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp25856 new file mode 100644 index 0000000..d46657b Binary files /dev/null and b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp25856 differ diff --git a/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp28148 b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp28148 new file mode 100644 index 0000000..d46657b Binary files /dev/null and b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp28148 differ diff --git a/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp31932 b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp31932 new file mode 100644 index 0000000..d46657b Binary files /dev/null and b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp31932 differ diff --git a/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp432 b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp432 new file mode 100644 index 0000000..d46657b Binary files /dev/null and b/apps/api/release/vendor/.prisma/client/query_engine-windows.dll.node.tmp432 differ