Add defaultLocale, EN names, and dashboard activity endpoints.

Expose branding.defaultLocale, optional user EN name fields, and lightweight dual daily activity for orders/customers; update project context and rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-01 09:35:27 +03:30
co-authored by Cursor
parent 4598add88c
commit 43e3912662
22 changed files with 331 additions and 15 deletions
+10
View File
@@ -19,6 +19,7 @@ import {
ListOrdersDto,
UpdateOrderDto,
} from './dto/order.dto';
import { DailyActivityQueryDto } from '../common/dto/daily-activity-query.dto';
import { OrdersService } from './orders.service';
@Controller('businesses/:businessId/orders')
@@ -35,6 +36,15 @@ export class OrdersController {
return this.service.list(businessId, query, user);
}
@Get('activity')
activity(
@Param('businessId') businessId: string,
@Query() query: DailyActivityQueryDto,
@CurrentUser() user: AuthUser,
) {
return this.service.dailyActivity(businessId, query.days, user);
}
@Get(':orderId')
getOne(
@Param('businessId') businessId: string,
+50 -1
View File
@@ -17,6 +17,10 @@ import { AuthUser } from '../auth/auth.types';
import { BusinessSettingsService } from '../business-settings/business-settings.service';
import { PermissionsService } from '../auth/permissions.service';
import { PrismaService } from '../prisma/prisma.service';
import {
buildDualDailyActivitySeries,
startOfLocalDay,
} from '../common/daily-activity';
import {
CreateAdminOrderDto,
ListOrdersDto,
@@ -168,6 +172,50 @@ export class OrdersService {
};
}
/** Lightweight daily counts for dashboard charts (admin only). */
async dailyActivity(
businessIdRaw: string,
daysRaw: number | undefined,
actor: AuthUser,
) {
const businessId = BigInt(businessIdRaw);
const scope = await this.resolveReadScope(businessId, actor);
if (scope !== 'admin') {
throw new ForbiddenException('Missing permission: orders.read for this business');
}
const days = Math.min(Math.max(daysRaw ?? 30, 1), 90);
const from = startOfLocalDay(days - 1);
const [orderRows, cartRows] = await Promise.all([
this.prisma.$queryRaw<{ day: Date; count: number }[]>(
Prisma.sql`
SELECT created_at::date AS day,
COUNT(*)::int AS count
FROM orders
WHERE business_id = ${businessId}
AND created_at >= ${from}
GROUP BY 1
ORDER BY 1
`,
),
this.prisma.$queryRaw<{ day: Date; count: number }[]>(
Prisma.sql`
SELECT ci.created_at::date AS day,
COUNT(*)::int AS count
FROM cart_items ci
JOIN carts c ON c.id = ci.cart_id
WHERE c.business_id = ${businessId}
AND ci.created_at >= ${from}
GROUP BY 1
ORDER BY 1
`,
),
]);
return buildDualDailyActivitySeries(days, orderRows, cartRows);
}
async getOne(businessIdRaw: string, orderIdRaw: string, actor: AuthUser) {
const businessId = BigInt(businessIdRaw);
const orderId = BigInt(orderIdRaw);
@@ -788,7 +836,7 @@ export class OrdersService {
private serialize(
order: OrderWithItems,
galleryByProductId: Map<string, string> = new Map(),
processSteps: { id: string; label: string; color: string }[] = [],
processSteps: { id: string; label: string; labelFa: string; color: string }[] = [],
) {
const processStep =
processSteps.find((step) => step.id === order.processStepId) ?? null;
@@ -800,6 +848,7 @@ export class OrdersService {
status: order.status,
processStepId: order.processStepId,
processStepLabel: processStep?.label ?? null,
processStepLabelFa: processStep?.labelFa?.trim() || null,
processStepColor: processStep?.color ?? null,
source: order.source,
subtotal: Number(order.subtotal),