Skip to content

Commit

Permalink
add product sales query
Browse files Browse the repository at this point in the history
  • Loading branch information
bahram1249 committed Jul 2, 2024
1 parent 90ae3dd commit 661051c
Show file tree
Hide file tree
Showing 14 changed files with 599 additions and 18 deletions.
2 changes: 2 additions & 0 deletions apps/e-commerce/src/e-commerce.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { ProductCommentStatusModule } from './admin/product-comment-status/produ
import { OrderStatusModule } from './admin/order-status/order-status.module';
import { OrderShipmentWayModule } from './admin/order-shipmentway/order-shipmentway.module';
import { AdminAddressModule } from './admin/address/address.module';
import { ProductSaleModule } from './report/product-sale/product-sale.module';

@Module({
imports: [
Expand Down Expand Up @@ -146,6 +147,7 @@ import { AdminAddressModule } from './admin/address/address.module';
OrderStatusModule,
OrderShipmentWayModule,
AdminAddressModule,
ProductSaleModule,
],
providers: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export class AdminSaleService {
.includeProduct()
.includeInventory()
.includeVendor()

.offset(filter.offset)
.limit(filter.limit);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IntersectionType } from '@nestjs/swagger';
import { ListFilter } from '@rahino/query-filter';
import { VendorSaleDto } from './vendor-sale-dto';

export class GetVendorSaleDto extends IntersectionType(
VendorSaleDto,
ListFilter,
) {}
1 change: 1 addition & 0 deletions apps/e-commerce/src/report/product-sale/dto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './get-vendor-sale.dto';
34 changes: 34 additions & 0 deletions apps/e-commerce/src/report/product-sale/dto/vendor-sale-dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ApiProperty } from '@nestjs/swagger';
import { I18nTranslations } from 'apps/main/src/generated/i18n.generated';
import { Type } from 'class-transformer';
import { IsInt, IsNumber, IsOptional, IsString } from 'class-validator';
import { i18nValidationMessage } from 'nestjs-i18n';

export class VendorSaleDto {
@ApiProperty({
required: true,
type: IsString,
description: 'beginDate',
})
@IsString()
beginDate: string;

@ApiProperty({
required: true,
type: IsString,
description: 'endDate',
})
@IsString()
endDate: string;

@ApiProperty({
required: true,
type: IsNumber,
description: 'vendorId',
})
@IsInt({
message: i18nValidationMessage<I18nTranslations>('validation.NUMBER'),
})
@Type(() => Number)
vendorId: number;
}
49 changes: 49 additions & 0 deletions apps/e-commerce/src/report/product-sale/product-sale.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
Controller,
Get,
HttpCode,
HttpStatus,
Query,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { CheckPermission } from '@rahino/permission-checker/decorator';
import { PermissionGuard } from '@rahino/permission-checker/guard';
import { JsonResponseTransformInterceptor } from '@rahino/response/interceptor';
import {
ApiBearerAuth,
ApiOperation,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import { JwtGuard } from '@rahino/auth/guard';
import { ProductSaleService } from './product-sale.service';
import { GetUser } from '@rahino/auth/decorator';
import { User } from '@rahino/database/models/core/user.entity';
import { GetVendorSaleDto } from './dto';

@ApiTags('Report-ProductSales')
@UseGuards(JwtGuard, PermissionGuard)
@UseInterceptors(JsonResponseTransformInterceptor)
@ApiBearerAuth()
@Controller({
path: '/api/ecommerce/report/productSales',
version: ['1'],
})
export class ProductSaleController {
constructor(private service: ProductSaleService) {}

@ApiOperation({ description: 'show all product sales' })
@CheckPermission({ permissionSymbol: 'ecommerce.report.productsales.getall' })
@Get('/')
@ApiQuery({
name: 'filter',
type: GetVendorSaleDto,
style: 'deepObject',
explode: true,
})
@HttpCode(HttpStatus.OK)
async findAll(@GetUser() user: User, @Query() filter: GetVendorSaleDto) {
return await this.service.findAll(user, filter);
}
}
21 changes: 21 additions & 0 deletions apps/e-commerce/src/report/product-sale/product-sale.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common';
import { ProductSaleService } from './product-sale.service';
import { ProductSaleController } from './product-sale.controller';
import { SequelizeModule } from '@nestjs/sequelize';
import { User } from '@rahino/database/models/core/user.entity';
import { Permission } from '@rahino/database/models/core/permission.entity';
import { ECOrderDetail } from '@rahino/database/models/ecommerce-eav/ec-order-detail.entity';
import { PersianDate } from '@rahino/database/models/core/view/persiandate.entity';
import { SaleQueryBuilderModule } from '../sale-query-builder/sale-query-builder.module';
import { UserVendorModule } from '@rahino/ecommerce/user/vendor/user-vendor.module';

@Module({
imports: [
SaleQueryBuilderModule,
SequelizeModule.forFeature([User, Permission, ECOrderDetail, PersianDate]),
UserVendorModule,
],
controllers: [ProductSaleController],
providers: [ProductSaleService],
})
export class ProductSaleModule {}
129 changes: 129 additions & 0 deletions apps/e-commerce/src/report/product-sale/product-sale.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
BadRequestException,
ForbiddenException,
Injectable,
} from '@nestjs/common';
import { GetVendorSaleDto } from './dto';
import { InjectModel } from '@nestjs/sequelize';
import { PersianDate } from '@rahino/database/models/core/view/persiandate.entity';
import { QueryOptionsBuilder } from '@rahino/query-filter/sequelize-query-builder';
import { I18nContext, I18nService } from 'nestjs-i18n';
import { I18nTranslations } from 'apps/main/src/generated/i18n.generated';
import { User } from '@rahino/database/models/core/user.entity';
import { UserVendorService } from '@rahino/ecommerce/user/vendor/user-vendor.service';
import { Knex } from 'knex';
import { InjectKnex } from 'nestjs-knex';
import { OrderStatusEnum } from '@rahino/ecommerce/util/enum';

@Injectable()
export class ProductSaleService {
constructor(
@InjectModel(PersianDate)
private readonly persianDateRepository: typeof PersianDate,
private readonly i18n: I18nService<I18nTranslations>,
private readonly userVendorService: UserVendorService,
@InjectKnex() private readonly knex: Knex,
) {}

async findAll(user: User, filter: GetVendorSaleDto) {
const isAccessToVendor = await this.userVendorService.isAccessToVendor(
user,
filter.vendorId,
);
if (!isAccessToVendor) {
throw new ForbiddenException(
this.i18n.t('ecommerce.dont_access_to_this_vendor', {
lang: I18nContext.current().lang,
}),
);
}
const isValidBeginDate = await this.isValidDate(filter.beginDate);
if (!isValidBeginDate) {
throw new BadRequestException(
this.i18n.t('ecommerce.date_is_invalid', {
lang: I18nContext.current().lang,
}),
);
}
const isValidEndDate = await this.isValidDate(filter.endDate);
if (!isValidEndDate) {
throw new BadRequestException(
this.i18n.t('ecommerce.date_is_invalid', {
lang: I18nContext.current().lang,
}),
);
}

const result = await this.knex('ECOrderDetails')
.leftJoin('ECOrders', 'ECOrderDetails.orderId', 'ECOrders.id')
.leftJoin('ECProducts', 'ECOrderDetails.productId', 'ECProducts.id')
.leftJoin('ECVendors', 'ECOrderDetails.vendorId', 'ECVendors.id')
.select(
'ECOrderDetails.vendorId',
'ECOrderDetails.productId',
this.knex.raw('ECProducts.title as productTitle'),
this.knex.raw('ECProducts.sku as productSku'),
this.knex.raw('ECProducts.slug as productSlug'),
this.knex.raw('ECVendors.name as vendorName'),
this.knex.raw('ECVendors.slug as vendorSlug'),
this.knex.raw('SUM(ECOrderDetails.qty) as qty'),
)
.where('ECOrders.orderStatusId', '!=', OrderStatusEnum.WaitingForPayment)
.where('ECOrderDetails.gregorianAtPersian', '>=', filter.beginDate)
.where('ECOrderDetails.gregorianAtPersian', '<=', filter.endDate)
.modify(function (queryBuilder) {
if (filter.vendorId) {
queryBuilder.where('ECOrderDetails.vendorId', filter.vendorId);
}
})
.groupBy(
'ECOrderDetails.vendorId',
'ECOrderDetails.productId',
'ECProducts.title',
'ECProducts.sku',
'ECProducts.slug',
'ECVendors.name',
'ECVendors.slug',
)
.orderBy('qty', 'desc')
.limit(filter.limit)
.offset(filter.offset);

const countQuery = await this.knex('ECOrderDetails')
.leftJoin('ECOrders', 'ECOrderDetails.orderId', 'ECOrders.id')
.leftJoin('ECProducts', 'ECOrderDetails.productId', 'ECProducts.id')
.leftJoin('ECVendors', 'ECOrderDetails.vendorId', 'ECVendors.id')
.select(this.knex.raw('COUNT(*) OVER () AS totalRecords'))
.where('ECOrders.orderStatusId', '!=', OrderStatusEnum.WaitingForPayment)
.where('ECOrderDetails.gregorianAtPersian', '>=', filter.beginDate)
.where('ECOrderDetails.gregorianAtPersian', '<=', filter.endDate)
.modify(function (queryBuilder) {
if (filter.vendorId) {
queryBuilder.where('ECOrderDetails.vendorId', filter.vendorId);
}
})
.groupBy(
'ECOrderDetails.vendorId',
'ECOrderDetails.productId',
'ECProducts.title',
'ECProducts.sku',
'ECProducts.slug',
'ECVendors.name',
'ECVendors.slug',
)
.first();

return {
result: result,
total: countQuery['totalRecords'],
};
}

async isValidDate(date: string) {
const findDate = await this.persianDateRepository.findOne(
new QueryOptionsBuilder().filter({ GregorianDate: date }).build(),
);
if (!findDate) return false;
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ export class SaleQueryBuilderService {
return this;
}

nest(flag: boolean) {
this.builder = this.builder.nest(flag);
return this;
}

group(group: GroupOption) {
this.builder = this.builder.group(group);
return this;
Expand Down
17 changes: 17 additions & 0 deletions apps/main/src/routes/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import * as path from 'path';
import { AppLanguageResolver } from '../i18nResolver/AppLanguageResolver';
import { ECommerceSmsModule } from '@rahino/ecommerce/util/sms/ecommerce-sms.module';
import { ECommmerceSmsService } from '@rahino/ecommerce/util/sms/ecommerce-sms.service';
import { KnexModule } from 'nestjs-knex';

@Module({
imports: [
Expand Down Expand Up @@ -68,6 +69,22 @@ import { ECommmerceSmsService } from '@rahino/ecommerce/util/sms/ecommerce-sms.s
],
}),
DatabaseModule,
KnexModule.forRootAsync({
useFactory: (config: ConfigService) => ({
config: {
client: 'mssql',
useNullAsDefault: true,
connection: {
host: config.get<string>('DB_HOST'),
port: Number(config.get<number>('DB_PORT')),
user: config.get<string>('DB_USER'),
password: config.get<string>('DB_PASS'),
database: config.get<string>('DB_NAME_DEVELOPMENT'),
},
},
}),
inject: [ConfigService],
}),
DBLoggerModule,
AutomapperModule.forRoot({
strategyInitializer: classes(),
Expand Down
Loading

0 comments on commit 661051c

Please sign in to comment.