@@ -792,6 +792,7 @@ export const enum CashShiftMessage {
|
|||||||
NOT_FOUND = 'شیفت صندوق یافت نشد',
|
NOT_FOUND = 'شیفت صندوق یافت نشد',
|
||||||
ALREADY_OPEN = 'یک شیفت صندوق باز از قبل وجود دارد',
|
ALREADY_OPEN = 'یک شیفت صندوق باز از قبل وجود دارد',
|
||||||
ALREADY_CLOSED = 'این شیفت صندوق قبلا بسته شده است',
|
ALREADY_CLOSED = 'این شیفت صندوق قبلا بسته شده است',
|
||||||
|
ACCESS_DENIED = 'شما به این شیفت صندوق دسترسی ندارید',
|
||||||
ADMIN_NOT_FOUND = 'مدیر یافت نشد',
|
ADMIN_NOT_FOUND = 'مدیر یافت نشد',
|
||||||
RESTAURANT_NOT_FOUND = 'رستوران یافت نشد',
|
RESTAURANT_NOT_FOUND = 'رستوران یافت نشد',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ export enum Permission {
|
|||||||
|
|
||||||
// Order Management
|
// Order Management
|
||||||
MANAGE_ORDERS = 'manage_orders',
|
MANAGE_ORDERS = 'manage_orders',
|
||||||
|
VIEW_ORDER_REPORTS = 'view_order_reports',
|
||||||
|
MANAGE_SHIFTS = 'manage_shifts',
|
||||||
|
VIEW_ALL_SHIFTS = 'view_all_shifts',
|
||||||
|
|
||||||
// User & Admin Management
|
// User & Admin Management
|
||||||
MANAGE_ADMINS = 'manage_admins',
|
MANAGE_ADMINS = 'manage_admins',
|
||||||
@@ -46,6 +49,9 @@ export const PermissionTitles: Record<Permission, string> = {
|
|||||||
[Permission.MANAGE_FOODS]: 'مدیریت غذاها',
|
[Permission.MANAGE_FOODS]: 'مدیریت غذاها',
|
||||||
[Permission.MANAGE_CATEGORIES]: 'مدیریت دستهبندیها',
|
[Permission.MANAGE_CATEGORIES]: 'مدیریت دستهبندیها',
|
||||||
[Permission.MANAGE_ORDERS]: 'مدیریت سفارشات',
|
[Permission.MANAGE_ORDERS]: 'مدیریت سفارشات',
|
||||||
|
[Permission.VIEW_ORDER_REPORTS]: 'مشاهده گزارشات سفارش',
|
||||||
|
[Permission.MANAGE_SHIFTS]: 'مدیریت شیفتها',
|
||||||
|
[Permission.VIEW_ALL_SHIFTS]: 'مشاهده همه شیفتها',
|
||||||
[Permission.MANAGE_ADMINS]: 'مدیریت مدیران',
|
[Permission.MANAGE_ADMINS]: 'مدیریت مدیران',
|
||||||
[Permission.MANAGE_USERS]: 'مدیریت کاربران',
|
[Permission.MANAGE_USERS]: 'مدیریت کاربران',
|
||||||
[Permission.VIEW_REPORTS]: 'مشاهده گزارشات',
|
[Permission.VIEW_REPORTS]: 'مشاهده گزارشات',
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { Permission } from 'src/common/enums/permission.enum';
|
|||||||
import { OpenCashShiftDto } from '../dto/open-cash-shift.dto';
|
import { OpenCashShiftDto } from '../dto/open-cash-shift.dto';
|
||||||
import { CloseCashShiftDto } from '../dto/close-cash-shift.dto';
|
import { CloseCashShiftDto } from '../dto/close-cash-shift.dto';
|
||||||
import { FindCashShiftsDto } from '../dto/find-cash-shifts.dto';
|
import { FindCashShiftsDto } from '../dto/find-cash-shifts.dto';
|
||||||
import { UpdateCashShiftDto } from '../dto/update-cash-shift.dto';
|
|
||||||
|
|
||||||
@ApiTags('cash-shifts')
|
@ApiTags('cash-shifts')
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@@ -18,7 +17,7 @@ export class CashShiftsController {
|
|||||||
constructor(private readonly cashShiftsService: CashShiftsService) {}
|
constructor(private readonly cashShiftsService: CashShiftsService) {}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.MANAGE_ORDERS)
|
@Permissions(Permission.MANAGE_SHIFTS)
|
||||||
@Post('admin/cash-shifts/open')
|
@Post('admin/cash-shifts/open')
|
||||||
@ApiOperation({ summary: 'Open a new cash shift' })
|
@ApiOperation({ summary: 'Open a new cash shift' })
|
||||||
@ApiBody({ type: OpenCashShiftDto })
|
@ApiBody({ type: OpenCashShiftDto })
|
||||||
@@ -27,21 +26,7 @@ export class CashShiftsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.MANAGE_ORDERS)
|
@Permissions(Permission.MANAGE_SHIFTS)
|
||||||
@Patch('admin/cash-shifts/:shiftId')
|
|
||||||
@ApiOperation({ summary: 'Update a cash shift' })
|
|
||||||
@ApiParam({ name: 'shiftId', description: 'Cash shift ID' })
|
|
||||||
@ApiBody({ type: UpdateCashShiftDto })
|
|
||||||
updateShift(
|
|
||||||
@Param('shiftId') shiftId: string,
|
|
||||||
@RestId() restId: string,
|
|
||||||
@Body() dto: UpdateCashShiftDto,
|
|
||||||
) {
|
|
||||||
return this.cashShiftsService.updateShift(shiftId, restId, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(Permission.MANAGE_ORDERS)
|
|
||||||
@Patch('admin/cash-shifts/:shiftId/close')
|
@Patch('admin/cash-shifts/:shiftId/close')
|
||||||
@ApiOperation({ summary: 'Close an open cash shift' })
|
@ApiOperation({ summary: 'Close an open cash shift' })
|
||||||
@ApiParam({ name: 'shiftId', description: 'Cash shift ID' })
|
@ApiParam({ name: 'shiftId', description: 'Cash shift ID' })
|
||||||
@@ -49,50 +34,59 @@ export class CashShiftsController {
|
|||||||
closeShift(
|
closeShift(
|
||||||
@Param('shiftId') shiftId: string,
|
@Param('shiftId') shiftId: string,
|
||||||
@RestId() restId: string,
|
@RestId() restId: string,
|
||||||
|
@AdminId() adminId: string,
|
||||||
@Body() dto: CloseCashShiftDto,
|
@Body() dto: CloseCashShiftDto,
|
||||||
) {
|
) {
|
||||||
return this.cashShiftsService.closeShift(shiftId, restId, dto);
|
return this.cashShiftsService.closeShift(shiftId, restId, adminId, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.MANAGE_ORDERS)
|
@Permissions(Permission.MANAGE_SHIFTS)
|
||||||
@Get('admin/cash-shifts/current/summary')
|
@Get('admin/cash-shifts/current/summary')
|
||||||
@ApiOperation({ summary: 'Calculate summary for the current open cash shift' })
|
@ApiOperation({ summary: 'Calculate summary for the current open cash shift' })
|
||||||
getCurrentShiftSummary(@RestId() restId: string) {
|
getCurrentShiftSummary(@RestId() restId: string, @AdminId() adminId: string) {
|
||||||
return this.cashShiftsService.calculateCurrentShiftSummary(restId);
|
return this.cashShiftsService.calculateCurrentShiftSummary(restId, adminId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.MANAGE_ORDERS)
|
@Permissions(Permission.MANAGE_SHIFTS)
|
||||||
@Get('admin/cash-shifts/current')
|
@Get('admin/cash-shifts/current')
|
||||||
@ApiOperation({ summary: 'Get the current open cash shift' })
|
@ApiOperation({ summary: 'Get the current open cash shift' })
|
||||||
getCurrentOpenShift(@RestId() restId: string) {
|
getCurrentOpenShift(@RestId() restId: string, @AdminId() adminId: string) {
|
||||||
return this.cashShiftsService.getCurrentOpenShift(restId);
|
return this.cashShiftsService.getCurrentOpenShift(restId, adminId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.MANAGE_ORDERS)
|
@Permissions(Permission.MANAGE_SHIFTS)
|
||||||
@Get('admin/cash-shifts')
|
@Get('admin/cash-shifts')
|
||||||
@ApiOperation({ summary: 'List cash shifts with pagination and filters' })
|
@ApiOperation({ summary: 'List cash shifts with pagination and filters' })
|
||||||
findAll(@RestId() restId: string, @Query() dto: FindCashShiftsDto) {
|
findAll(@RestId() restId: string, @AdminId() adminId: string, @Query() dto: FindCashShiftsDto) {
|
||||||
return this.cashShiftsService.findAll(restId, dto);
|
return this.cashShiftsService.findAll(restId, adminId, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.MANAGE_ORDERS)
|
@Permissions(Permission.MANAGE_SHIFTS)
|
||||||
@Get('admin/cash-shifts/:shiftId/summary')
|
@Get('admin/cash-shifts/:shiftId/summary')
|
||||||
@ApiOperation({ summary: 'Calculate summary for a cash shift' })
|
@ApiOperation({ summary: 'Calculate summary for a cash shift' })
|
||||||
@ApiParam({ name: 'shiftId', description: 'Cash shift ID' })
|
@ApiParam({ name: 'shiftId', description: 'Cash shift ID' })
|
||||||
getShiftSummary(@Param('shiftId') shiftId: string, @RestId() restId: string) {
|
getShiftSummary(
|
||||||
return this.cashShiftsService.calculateShiftSummary(shiftId, restId);
|
@Param('shiftId') shiftId: string,
|
||||||
|
@RestId() restId: string,
|
||||||
|
@AdminId() adminId: string,
|
||||||
|
) {
|
||||||
|
return this.cashShiftsService.calculateShiftSummary(shiftId, restId, adminId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.MANAGE_ORDERS)
|
@Permissions(Permission.MANAGE_SHIFTS)
|
||||||
@Get('admin/cash-shifts/:shiftId')
|
@Get('admin/cash-shifts/:shiftId')
|
||||||
@ApiOperation({ summary: 'Get a cash shift by ID' })
|
@ApiOperation({ summary: 'Get a cash shift by ID' })
|
||||||
@ApiParam({ name: 'shiftId', description: 'Cash shift ID' })
|
@ApiParam({ name: 'shiftId', description: 'Cash shift ID' })
|
||||||
findOne(@Param('shiftId') shiftId: string, @RestId() restId: string) {
|
findOne(
|
||||||
return this.cashShiftsService.findOne(shiftId, restId);
|
@Param('shiftId') shiftId: string,
|
||||||
|
@RestId() restId: string,
|
||||||
|
@AdminId() adminId: string,
|
||||||
|
) {
|
||||||
|
return this.cashShiftsService.findOne(shiftId, restId, adminId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export class OrdersController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.VIEW_REPORTS)
|
@Permissions(Permission.VIEW_ORDER_REPORTS)
|
||||||
@ApiOperation({ summary: 'Get order report grouped by food' })
|
@ApiOperation({ summary: 'Get order report grouped by food' })
|
||||||
@Get('admin/orders/report/by-food')
|
@Get('admin/orders/report/by-food')
|
||||||
getFoodOrderReport(@RestId() restId: string, @Query() dto: FoodOrderReportDto) {
|
getFoodOrderReport(@RestId() restId: string, @Query() dto: FoodOrderReportDto) {
|
||||||
@@ -154,7 +154,7 @@ export class OrdersController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.VIEW_REPORTS)
|
@Permissions(Permission.VIEW_ORDER_REPORTS)
|
||||||
@ApiOperation({ summary: 'Get order report grouped by day' })
|
@ApiOperation({ summary: 'Get order report grouped by day' })
|
||||||
@Get('admin/orders/report/by-day')
|
@Get('admin/orders/report/by-day')
|
||||||
getDailyOrderReport(@RestId() restId: string, @Query() dto: DailyOrderReportDto) {
|
getDailyOrderReport(@RestId() restId: string, @Query() dto: DailyOrderReportDto) {
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
||||||
import { Type } from 'class-transformer';
|
|
||||||
import { IsNumber, IsOptional, IsString, Min } from 'class-validator';
|
|
||||||
|
|
||||||
export class UpdateCashShiftDto {
|
|
||||||
@ApiPropertyOptional({ description: 'Opening cash amount in drawer', minimum: 0 })
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
openingAmount?: number;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
notes?: string;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { EntityManager } from '@mikro-orm/postgresql';
|
import { EntityManager } from '@mikro-orm/postgresql';
|
||||||
import { CashShift } from '../entities/cash-shift.entity';
|
import { CashShift } from '../entities/cash-shift.entity';
|
||||||
import { CashShiftStatus, CashShiftSummary } from '../interface/cash-shift.interface';
|
import { CashShiftStatus, CashShiftSummary } from '../interface/cash-shift.interface';
|
||||||
@@ -6,19 +6,21 @@ import { CashShiftRepository } from '../repositories/cash-shift.repository';
|
|||||||
import { OpenCashShiftDto } from '../dto/open-cash-shift.dto';
|
import { OpenCashShiftDto } from '../dto/open-cash-shift.dto';
|
||||||
import { CloseCashShiftDto } from '../dto/close-cash-shift.dto';
|
import { CloseCashShiftDto } from '../dto/close-cash-shift.dto';
|
||||||
import { FindCashShiftsDto } from '../dto/find-cash-shifts.dto';
|
import { FindCashShiftsDto } from '../dto/find-cash-shifts.dto';
|
||||||
import { UpdateCashShiftDto } from '../dto/update-cash-shift.dto';
|
|
||||||
import { Admin } from '../../admin/entities/admin.entity';
|
import { Admin } from '../../admin/entities/admin.entity';
|
||||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||||
import { Order } from '../entities/order.entity';
|
import { Order } from '../entities/order.entity';
|
||||||
import { CashShiftMessage } from 'src/common/enums/message.enum';
|
import { CashShiftMessage } from 'src/common/enums/message.enum';
|
||||||
|
import { Permission } from 'src/common/enums/permission.enum';
|
||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||||
import { calculateCashShiftSummary } from '../utils/calculate-cash-shift-summary.util';
|
import { calculateCashShiftSummary } from '../utils/calculate-cash-shift-summary.util';
|
||||||
|
import { PermissionsService } from 'src/modules/roles/providers/permissions.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CashShiftsService {
|
export class CashShiftsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
private readonly cashShiftRepository: CashShiftRepository,
|
private readonly cashShiftRepository: CashShiftRepository,
|
||||||
|
private readonly permissionsService: PermissionsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async openShift(restaurantId: string, adminId: string, dto: OpenCashShiftDto): Promise<CashShift> {
|
async openShift(restaurantId: string, adminId: string, dto: OpenCashShiftDto): Promise<CashShift> {
|
||||||
@@ -54,7 +56,12 @@ export class CashShiftsService {
|
|||||||
return shift;
|
return shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateShift(shiftId: string, restaurantId: string, dto: UpdateCashShiftDto): Promise<CashShift> {
|
async closeShift(
|
||||||
|
shiftId: string,
|
||||||
|
restaurantId: string,
|
||||||
|
adminId: string,
|
||||||
|
dto: CloseCashShiftDto,
|
||||||
|
): Promise<CashShift> {
|
||||||
const shift = await this.em.findOne(
|
const shift = await this.em.findOne(
|
||||||
CashShift,
|
CashShift,
|
||||||
{ id: shiftId, restaurant: { id: restaurantId } },
|
{ id: shiftId, restaurant: { id: restaurantId } },
|
||||||
@@ -65,40 +72,13 @@ export class CashShiftsService {
|
|||||||
throw new NotFoundException(CashShiftMessage.NOT_FOUND);
|
throw new NotFoundException(CashShiftMessage.NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.openingAmount !== undefined) {
|
await this.assertShiftAccess(shift, adminId, restaurantId);
|
||||||
shift.openingAmount = dto.openingAmount;
|
|
||||||
|
|
||||||
if (shift.status === CashShiftStatus.CLOSED && shift.countedAmount !== null) {
|
|
||||||
const summary = await this.calculateShiftSummary(shiftId, restaurantId);
|
|
||||||
shift.expectedAmount = summary.expectedAmount;
|
|
||||||
shift.differenceAmount = Number(shift.countedAmount) - summary.expectedAmount;
|
|
||||||
shift.ordersCount = summary.ordersCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dto.notes !== undefined) {
|
|
||||||
shift.notes = dto.notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.em.persistAndFlush(shift);
|
|
||||||
return shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
async closeShift(shiftId: string, restaurantId: string, dto: CloseCashShiftDto): Promise<CashShift> {
|
|
||||||
const shift = await this.em.findOne(
|
|
||||||
CashShift,
|
|
||||||
{ id: shiftId, restaurant: { id: restaurantId } },
|
|
||||||
{ populate: ['admin', 'restaurant'] },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!shift) {
|
|
||||||
throw new NotFoundException(CashShiftMessage.NOT_FOUND);
|
|
||||||
}
|
|
||||||
if (shift.status === CashShiftStatus.CLOSED) {
|
if (shift.status === CashShiftStatus.CLOSED) {
|
||||||
throw new BadRequestException(CashShiftMessage.ALREADY_CLOSED);
|
throw new BadRequestException(CashShiftMessage.ALREADY_CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
const summary = await this.calculateShiftSummary(shiftId, restaurantId);
|
const summary = await this.calculateShiftSummary(shiftId, restaurantId, adminId);
|
||||||
const countedAmount = dto.countedAmount;
|
const countedAmount = dto.countedAmount;
|
||||||
|
|
||||||
shift.expectedAmount = summary.expectedAmount;
|
shift.expectedAmount = summary.expectedAmount;
|
||||||
@@ -115,18 +95,21 @@ export class CashShiftsService {
|
|||||||
return shift;
|
return shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
async calculateShiftSummary(shiftId: string, restaurantId: string): Promise<CashShiftSummary> {
|
async calculateShiftSummary(
|
||||||
const shift = await this.em.findOne(CashShift, { id: shiftId, restaurant: { id: restaurantId } });
|
shiftId: string,
|
||||||
if (!shift) {
|
restaurantId: string,
|
||||||
throw new NotFoundException(CashShiftMessage.NOT_FOUND);
|
adminId: string,
|
||||||
}
|
): Promise<CashShiftSummary> {
|
||||||
|
const shift = await this.findOne(shiftId, restaurantId, adminId);
|
||||||
const orders = await this.getShiftOrders(shift, restaurantId);
|
const orders = await this.getShiftOrders(shift, restaurantId);
|
||||||
return calculateCashShiftSummary(Number(shift.openingAmount), orders);
|
return calculateCashShiftSummary(Number(shift.openingAmount), orders);
|
||||||
}
|
}
|
||||||
|
|
||||||
async calculateCurrentShiftSummary(restaurantId: string): Promise<CashShiftSummary | null> {
|
async calculateCurrentShiftSummary(
|
||||||
const shift = await this.cashShiftRepository.findOpenShift(restaurantId);
|
restaurantId: string,
|
||||||
|
adminId: string,
|
||||||
|
): Promise<CashShiftSummary | null> {
|
||||||
|
const shift = await this.getCurrentOpenShift(restaurantId, adminId);
|
||||||
if (!shift) {
|
if (!shift) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -135,11 +118,22 @@ export class CashShiftsService {
|
|||||||
return calculateCashShiftSummary(Number(shift.openingAmount), orders);
|
return calculateCashShiftSummary(Number(shift.openingAmount), orders);
|
||||||
}
|
}
|
||||||
|
|
||||||
findAll(restaurantId: string, dto: FindCashShiftsDto): Promise<PaginatedResult<CashShift>> {
|
async findAll(
|
||||||
return this.cashShiftRepository.findAllPaginated(restaurantId, dto);
|
restaurantId: string,
|
||||||
|
adminId: string,
|
||||||
|
dto: FindCashShiftsDto,
|
||||||
|
): Promise<PaginatedResult<CashShift>> {
|
||||||
|
const canViewAll = await this.canViewAllShifts(adminId, restaurantId);
|
||||||
|
const query = { ...dto };
|
||||||
|
|
||||||
|
if (!canViewAll) {
|
||||||
|
query.adminId = adminId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cashShiftRepository.findAllPaginated(restaurantId, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(shiftId: string, restaurantId: string): Promise<CashShift> {
|
async findOne(shiftId: string, restaurantId: string, adminId: string): Promise<CashShift> {
|
||||||
const shift = await this.em.findOne(
|
const shift = await this.em.findOne(
|
||||||
CashShift,
|
CashShift,
|
||||||
{ id: shiftId, restaurant: { id: restaurantId } },
|
{ id: shiftId, restaurant: { id: restaurantId } },
|
||||||
@@ -150,11 +144,13 @@ export class CashShiftsService {
|
|||||||
throw new NotFoundException(CashShiftMessage.NOT_FOUND);
|
throw new NotFoundException(CashShiftMessage.NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.assertShiftAccess(shift, adminId, restaurantId);
|
||||||
return shift;
|
return shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentOpenShift(restaurantId: string): Promise<CashShift | null> {
|
async getCurrentOpenShift(restaurantId: string, adminId: string): Promise<CashShift | null> {
|
||||||
return this.cashShiftRepository.findOpenShift(restaurantId);
|
const canViewAll = await this.canViewAllShifts(adminId, restaurantId);
|
||||||
|
return this.cashShiftRepository.findOpenShift(restaurantId, canViewAll ? undefined : adminId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async assignOrderToOpenShift(order: Order, em: EntityManager = this.em): Promise<void> {
|
async assignOrderToOpenShift(order: Order, em: EntityManager = this.em): Promise<void> {
|
||||||
@@ -185,4 +181,25 @@ export class CashShiftsService {
|
|||||||
{ populate: ['payments'] },
|
{ populate: ['payments'] },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async canViewAllShifts(adminId: string, restaurantId: string): Promise<boolean> {
|
||||||
|
const permissions = await this.permissionsService.getAdminPermissions(adminId, restaurantId);
|
||||||
|
return permissions.includes(Permission.VIEW_ALL_SHIFTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async assertShiftAccess(
|
||||||
|
shift: CashShift,
|
||||||
|
adminId: string,
|
||||||
|
restaurantId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const canViewAll = await this.canViewAllShifts(adminId, restaurantId);
|
||||||
|
if (canViewAll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shiftAdminId = typeof shift.admin === 'string' ? shift.admin : shift.admin.id;
|
||||||
|
if (shiftAdminId !== adminId) {
|
||||||
|
throw new ForbiddenException(CashShiftMessage.ACCESS_DENIED);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,10 +73,16 @@ export class CashShiftRepository extends EntityRepository<CashShift> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
findOpenShift(restId: string): Promise<CashShift | null> {
|
findOpenShift(restId: string, adminId?: string): Promise<CashShift | null> {
|
||||||
return this.findOne(
|
const where: FilterQuery<CashShift> = {
|
||||||
{ restaurant: { id: restId }, status: CashShiftStatus.OPEN },
|
restaurant: { id: restId },
|
||||||
{ populate: ['admin'] },
|
status: CashShiftStatus.OPEN,
|
||||||
);
|
};
|
||||||
|
|
||||||
|
if (adminId) {
|
||||||
|
where.admin = { id: adminId };
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.findOne(where, { populate: ['admin'] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,58 +124,10 @@ export class OrderRepository extends EntityRepository<Order> {
|
|||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
||||||
populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'cashShift', 'items', 'items.food'] as never,
|
populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'cashShift', 'payments'] as never,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Collect all (orderId, foodId) pairs for efficient review lookup
|
const totalPages = Math.ceil(total / limit);
|
||||||
const orderFoodPairs: Array<{ orderId: string; foodId: string }> = [];
|
|
||||||
for (const order of data) {
|
|
||||||
for (const item of order.items.getItems()) {
|
|
||||||
if (item.food?.id) {
|
|
||||||
orderFoodPairs.push({ orderId: order.id, foodId: item.food.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all relevant reviews in a single query
|
|
||||||
const reviewsMap: Map<string, string> = new Map();
|
|
||||||
if (orderFoodPairs.length > 0) {
|
|
||||||
const orderIds = [...new Set(orderFoodPairs.map(p => p.orderId))];
|
|
||||||
const foodIds = [...new Set(orderFoodPairs.map(p => p.foodId))];
|
|
||||||
|
|
||||||
const reviews = await this.em.find(
|
|
||||||
Review,
|
|
||||||
{
|
|
||||||
order: { id: { $in: orderIds } },
|
|
||||||
food: { id: { $in: foodIds } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fields: ['id', 'order', 'food'],
|
|
||||||
populate: ['order', 'food'],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a map: key = `${orderId}-${foodId}`, value = reviewId
|
|
||||||
for (const review of reviews) {
|
|
||||||
const key = `${review.order.id}-${review.food.id}`;
|
|
||||||
reviewsMap.set(key, review.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map reviewIds to foods
|
|
||||||
for (const order of data) {
|
|
||||||
for (const item of order.items.getItems()) {
|
|
||||||
if (item.food) {
|
|
||||||
const key = `${order.id}-${item.food.id}`;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
const food = item.food as any;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
food.reviewId = reviewsMap.get(key) || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(total / limit);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ export const rolesData: RoleConfig[] = [
|
|||||||
name: 'حسابدار ',
|
name: 'حسابدار ',
|
||||||
isSystem: false,
|
isSystem: false,
|
||||||
permissionFilter: (name: Permission) =>
|
permissionFilter: (name: Permission) =>
|
||||||
name === Permission.VIEW_REPORTS || name === Permission.MANAGE_PAYMENTS || name === Permission.MANAGE_ORDERS,
|
name === Permission.VIEW_REPORTS ||
|
||||||
|
name === Permission.VIEW_ORDER_REPORTS ||
|
||||||
|
name === Permission.MANAGE_PAYMENTS ||
|
||||||
|
name === Permission.MANAGE_ORDERS,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user