From 1a3a450960a0466cc1cfd2ea503b63bdc87471e2 Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Sat, 18 Apr 2026 12:14:19 +0330 Subject: [PATCH] debugging and add image uploader to guild good --- .../admin/consumers/consumers.service.ts | 43 +----- .../admin/guilds/goods/goods.controller.ts | 49 +++++- .../admin/guilds/goods/goods.module.ts | 3 +- .../admin/guilds/goods/goods.service.ts | 145 ++++++++++++------ .../guilds/goods/images/images.service.ts | 5 +- src/modules/consumer/consumer.module.ts | 2 + src/modules/consumer/consumer.service.ts | 23 +-- .../saleInvoices/saleInvoices.controller.ts | 17 +- .../saleInvoices/saleInvoices.service.ts | 10 +- src/modules/consumer/utils/index.ts | 1 + .../utils/mapConsumerWithLicense.util.ts | 35 +++++ src/modules/partners/partners.middleware.ts | 2 - src/modules/pos/goods/goods.service.ts | 1 + src/modules/pos/pos.controller.ts | 2 - src/modules/storage/storage.service.ts | 3 +- src/modules/uploader/uploader.service.ts | 17 +- 16 files changed, 214 insertions(+), 144 deletions(-) create mode 100644 src/modules/consumer/utils/index.ts create mode 100644 src/modules/consumer/utils/mapConsumerWithLicense.util.ts diff --git a/src/modules/admin/consumers/consumers.service.ts b/src/modules/admin/consumers/consumers.service.ts index c3e8d4b..5134673 100644 --- a/src/modules/admin/consumers/consumers.service.ts +++ b/src/modules/admin/consumers/consumers.service.ts @@ -6,6 +6,7 @@ import { PartnerStatus, } from '@/generated/prisma/enums' import { ConsumerCreateInput, ConsumerSelect } from '@/generated/prisma/models' +import mapConsumerWithLicenseUtil from '@/modules/consumer/utils/mapConsumerWithLicense.util' import { PrismaService } from '@/prisma/prisma.service' import { BadRequestException, Injectable } from '@nestjs/common' import { ResponseMapper } from 'common/response/response-mapper' @@ -45,44 +46,6 @@ export class AdminConsumersService { }, } - private readonly mapConsumer = (consumer: any) => { - const { activated_licenses, ...rest } = consumer - - let lastLicense: any = null - - if (consumer.activated_licenses.length) { - lastLicense = consumer.activated_licenses[0] - if (consumer.activated_licenses.length > 1) { - const activeLicenseInfo = consumer.activated_licenses.find( - activated_license => activated_license.expires_at, - ) - - if (!activeLicenseInfo) { - consumer.activated_licenses.sort((a, b) => - a.expires_at > b.expires_at ? 1 : -1, - ) - lastLicense = consumer.activated_licenses[0] - } - } - } - - return { - ...rest, - fullname: `${rest?.first_name} ${rest?.last_name}`, - license_info: this.prepareLicenseInfo(lastLicense), - } - } - - private readonly prepareLicenseInfo = (latestLicense: any) => { - if (!latestLicense) return null - const { license, ...rest } = latestLicense - - return { - ...rest, - partner: license.charged_license_transaction.partner, - } - } - async findAll() { const [consumers, count] = await this.prisma.$transaction([ this.prisma.consumer.findMany({ @@ -91,7 +54,7 @@ export class AdminConsumersService { this.prisma.consumer.count(), ]) - return ResponseMapper.paginate(consumers.map(this.mapConsumer), { count }) + return ResponseMapper.paginate(consumers.map(mapConsumerWithLicenseUtil), { count }) } async findOne(id: string) { @@ -100,7 +63,7 @@ export class AdminConsumersService { select: this.defaultSelect, }) - return ResponseMapper.single(this.mapConsumer(consumer)) + return ResponseMapper.single(mapConsumerWithLicenseUtil(consumer)) } async create(data: CreateConsumerDto) { diff --git a/src/modules/admin/guilds/goods/goods.controller.ts b/src/modules/admin/guilds/goods/goods.controller.ts index 1a40e73..6ab14e4 100644 --- a/src/modules/admin/guilds/goods/goods.controller.ts +++ b/src/modules/admin/guilds/goods/goods.controller.ts @@ -1,5 +1,17 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common' -import { CreateGuildGoodDto } from './dto/create-good.dto' +import { multerImageOptions } from '@/multer.config' +import { + Body, + Controller, + Get, + Param, + Patch, + Post, + UploadedFile, + UseInterceptors, +} from '@nestjs/common' +import { FileInterceptor } from '@nestjs/platform-express' +import { ApiBody, ApiConsumes } from '@nestjs/swagger' +import { CreateGuildGoodDto, UpdateGuildGoodDto } from './dto/create-good.dto' import { GoodsService } from './goods.service' @Controller('admin/guilds/:guildId/goods') @@ -17,7 +29,36 @@ export class GoodsController { } @Post() - create(@Body() data: CreateGuildGoodDto) { - return this.goodsService.create(data) + @UseInterceptors(FileInterceptor('file', multerImageOptions)) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + image: { type: 'string', format: 'binary' }, + }, + }, + }) + create(@Body() data: CreateGuildGoodDto, @UploadedFile() file: Express.Multer.File) { + return this.goodsService.create(data, file) + } + + @Patch(':id') + @UseInterceptors(FileInterceptor('image', multerImageOptions)) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + image: { type: 'string', format: 'binary' }, + }, + }, + }) + update( + @Param('id') id: string, + @Body() data: UpdateGuildGoodDto, + @UploadedFile() file: Express.Multer.File, + ) { + return this.goodsService.update(id, data, file) } } diff --git a/src/modules/admin/guilds/goods/goods.module.ts b/src/modules/admin/guilds/goods/goods.module.ts index cfeae25..cde4977 100644 --- a/src/modules/admin/guilds/goods/goods.module.ts +++ b/src/modules/admin/guilds/goods/goods.module.ts @@ -1,3 +1,4 @@ +import { UploaderModule } from '@/modules/uploader/uploader.module' import { PrismaModule } from '@/prisma/prisma.module' import { Module } from '@nestjs/common' import { GoodsController } from './goods.controller' @@ -5,7 +6,7 @@ import { GoodsService } from './goods.service' import { GuildGoodImagesModule } from './images/images.module' @Module({ - imports: [PrismaModule, GuildGoodImagesModule], + imports: [PrismaModule, GuildGoodImagesModule, UploaderModule], controllers: [GoodsController], providers: [GoodsService], }) diff --git a/src/modules/admin/guilds/goods/goods.service.ts b/src/modules/admin/guilds/goods/goods.service.ts index fc1285a..4bfdd4e 100644 --- a/src/modules/admin/guilds/goods/goods.service.ts +++ b/src/modules/admin/guilds/goods/goods.service.ts @@ -1,39 +1,46 @@ -import { GoodOmit } from '@/generated/prisma/models' +import { UploadedFileTypes } from '@/common/enums/enums' +import { GoodSelect, GoodWhereInput } from '@/generated/prisma/models' +import { UploaderService } from '@/modules/uploader/uploader.service' import { PrismaService } from '@/prisma/prisma.service' import { Injectable } from '@nestjs/common' import { ResponseMapper } from 'common/response/response-mapper' -import { CreateGuildGoodDto } from './dto/create-good.dto' +import { CreateGuildGoodDto, UpdateGuildGoodDto } from './dto/create-good.dto' @Injectable() export class GoodsService { - constructor(private prisma: PrismaService) {} + constructor( + private prisma: PrismaService, + private readonly uploaderService: UploaderService, + ) {} - private readonly goodOmit = { - complex_id: true, - barcode: true, - base_sale_price: true, + private readonly defaultSelect: GoodSelect = { + id: true, + name: true, + pricing_model: true, + unit_type: true, + image_url: true, + sku: true, + description: true, + category: { + select: { + id: true, + name: true, + }, + }, + } + + private readonly defaultWhere = (guild_id: string): GoodWhereInput => ({ is_default_guild_good: true, - local_sku: true, - } as GoodOmit + category: { + guild_id, + }, + }) async findAll(guildId: string) { const [goods, count] = await this.prisma.$transaction([ this.prisma.good.findMany({ - where: { - is_default_guild_good: true, - category: { - guild_id: guildId, - }, - }, - include: { - category: { - select: { - id: true, - name: true, - }, - }, - }, - omit: this.goodOmit, + where: this.defaultWhere(guildId), + select: this.defaultSelect, }), this.prisma.good.count(), ]) @@ -46,41 +53,81 @@ export class GoodsService { const good = await this.prisma.good.findUnique({ where: { id, - is_default_guild_good: true, - category: { - guild_id, - }, + ...(this.defaultWhere(guild_id) as any), }, - - include: { - category: { - select: { - id: true, - name: true, - }, - }, - }, - - omit: this.goodOmit, + select: this.defaultSelect, }) return ResponseMapper.single(good) } - async create(data: CreateGuildGoodDto) { + async create(data: CreateGuildGoodDto, file: Express.Multer.File) { const { category_id, ...rest } = data - const good = await this.prisma.good.create({ - data: { - category: { - connect: { - id: category_id, + const good = await this.prisma.$transaction(async tx => { + let image_url = '' + if (file) { + const uploadedUrl = await this.uploaderService.uploadFile( + file, + UploadedFileTypes.GOOD, + ) + image_url = uploadedUrl || '' + } + + return await tx.good.create({ + data: { + ...rest, + is_default_guild_good: true, + image_url, + category: { + connect: { + id: category_id, + }, }, }, - ...rest, - is_default_guild_good: true, - }, - omit: this.goodOmit, + select: this.defaultSelect, + }) + }) + + return ResponseMapper.create(good) + } + + async update(id: string, data: UpdateGuildGoodDto, file: Express.Multer.File) { + const { category_id, ...rest } = data + + const good = await this.prisma.$transaction(async tx => { + let image_url = '' + if (file) { + const uploadedUrl = await this.uploaderService.uploadFile( + file, + UploadedFileTypes.GOOD, + ) + image_url = uploadedUrl || '' + } + + const prevImage = await tx.good.findUnique({ + where: { id }, + select: { image_url: true }, + }) + + if (image_url && prevImage?.image_url) { + this.uploaderService.deleteFile(prevImage.image_url, UploadedFileTypes.GOOD) + } + + return await tx.good.update({ + where: { id }, + data: { + ...rest, + is_default_guild_good: true, + image_url, + category: { + connect: { + id: category_id, + }, + }, + }, + select: this.defaultSelect, + }) }) return ResponseMapper.create(good) diff --git a/src/modules/admin/guilds/goods/images/images.service.ts b/src/modules/admin/guilds/goods/images/images.service.ts index 2ec1218..3e118ba 100644 --- a/src/modules/admin/guilds/goods/images/images.service.ts +++ b/src/modules/admin/guilds/goods/images/images.service.ts @@ -23,7 +23,10 @@ export class GuildGoodImagesService { const url = await this.uploadFileService.uploadFile(file, UploadedFileTypes.GOOD) if (url) { if (currentImage.image_url) { - this.uploadFileService.deleteFile(currentImage.image_url) + this.uploadFileService.deleteFile( + currentImage.image_url, + UploadedFileTypes.GOOD, + ) } return this.prisma.good.update({ where: { diff --git a/src/modules/consumer/consumer.module.ts b/src/modules/consumer/consumer.module.ts index f1d4d80..26cdf62 100644 --- a/src/modules/consumer/consumer.module.ts +++ b/src/modules/consumer/consumer.module.ts @@ -7,6 +7,7 @@ import { ConsumerMiddleware } from './consumer.middleware' import { ConsumerService } from './consumer.service' import { ConsumerCustomersModule } from './customers/customers.module' import { ConsumerPosesModule } from './poses/poses.module' +import { ConsumerSaleInvoicesModule } from './saleInvoices/saleInvoices.module' import { ConsumerStatisticsModule } from './statistics/statistics.module' @Module({ @@ -17,6 +18,7 @@ import { ConsumerStatisticsModule } from './statistics/statistics.module' ConsumerCustomersModule, ConsumerStatisticsModule, ConsumerPosesModule, + ConsumerSaleInvoicesModule, ], providers: [JwtService, ConsumerService], }) diff --git a/src/modules/consumer/consumer.service.ts b/src/modules/consumer/consumer.service.ts index 19560bc..5bbdfb8 100644 --- a/src/modules/consumer/consumer.service.ts +++ b/src/modules/consumer/consumer.service.ts @@ -1,6 +1,7 @@ import { ResponseMapper } from '@/common/response/response-mapper' import { PrismaService } from '@/prisma/prisma.service' import { Injectable } from '@nestjs/common' +import mapConsumerWithLicenseUtil from './utils/mapConsumerWithLicense.util' @Injectable() export class ConsumerService { @@ -41,26 +42,6 @@ export class ConsumerService { }, }) - const { activated_licenses, ...rest } = consumer - - const mappedData = { - ...rest, - activated_licenses: activated_licenses.map(activatedLicense => { - const { license, ...rest } = activatedLicense - return { - ...rest, - license: { - id: license.id, - partner: license.charged_license_transaction.partner, - }, - } - }), - } - - return ResponseMapper.single({ - ...consumer, - full_name: `${consumer?.first_name} ${consumer?.last_name}`, - activated_licenses: mappedData, - }) + return ResponseMapper.single(mapConsumerWithLicenseUtil(consumer)) } } diff --git a/src/modules/consumer/saleInvoices/saleInvoices.controller.ts b/src/modules/consumer/saleInvoices/saleInvoices.controller.ts index 9d38a2d..0713163 100644 --- a/src/modules/consumer/saleInvoices/saleInvoices.controller.ts +++ b/src/modules/consumer/saleInvoices/saleInvoices.controller.ts @@ -1,15 +1,20 @@ import { TokenAccount } from '@/common/decorators/tokenInfo.decorator' -import { Controller, Get } from '@nestjs/common' +import { Controller, Get, Param } from '@nestjs/common' import { ApiTags } from '@nestjs/swagger' import { SaleInvoicesService } from './saleInvoices.service' -@ApiTags('ConsumerStatistics') -@Controller('consumer/statistics') +@ApiTags('ConsumerSaleInvoices') +@Controller('consumer/sale-invoices') export class StatisticsController { constructor(private readonly service: SaleInvoicesService) {} - @Get('invoices') - async getInvoices(@TokenAccount('userId') consumerId: string) { - return this.service.getInvoices(consumerId) + @Get('') + async findAll(@TokenAccount('userId') consumerId: string) { + return this.service.findAll(consumerId) + } + + @Get(':id') + async findOne(@TokenAccount('userId') consumerId: string, @Param('id') id: string) { + return this.service.findOne(consumerId, id) } } diff --git a/src/modules/consumer/saleInvoices/saleInvoices.service.ts b/src/modules/consumer/saleInvoices/saleInvoices.service.ts index a82d7bd..e9e6169 100644 --- a/src/modules/consumer/saleInvoices/saleInvoices.service.ts +++ b/src/modules/consumer/saleInvoices/saleInvoices.service.ts @@ -64,7 +64,7 @@ export class SaleInvoicesService { }, } - async getInvoices(consumer_id: string, page = 1, pageSize = 10) { + async findAll(consumer_id: string, page = 1, pageSize = 10) { const invoicesWhere: SalesInvoiceWhereInput = { consumer_account: { consumer_id, @@ -82,7 +82,7 @@ export class SaleInvoicesService { return ResponseMapper.paginate(invoices, { page, pageSize, count }) } - async getInvoice(consumer_id: string, id: string) { + async findOne(consumer_id: string, id: string) { const invoicesWhere: SalesInvoiceWhereUniqueInput = { id, consumer_account: { @@ -95,6 +95,12 @@ export class SaleInvoicesService { ...this.defaultSelect, notes: true, created_at: true, + payments: { + select: { + amount: true, + payment_method: true, + }, + }, items: { select: { id: true, diff --git a/src/modules/consumer/utils/index.ts b/src/modules/consumer/utils/index.ts new file mode 100644 index 0000000..7a969c5 --- /dev/null +++ b/src/modules/consumer/utils/index.ts @@ -0,0 +1 @@ +export * from './mapConsumerWithLicense.util' diff --git a/src/modules/consumer/utils/mapConsumerWithLicense.util.ts b/src/modules/consumer/utils/mapConsumerWithLicense.util.ts new file mode 100644 index 0000000..b53f318 --- /dev/null +++ b/src/modules/consumer/utils/mapConsumerWithLicense.util.ts @@ -0,0 +1,35 @@ +export default (consumer: any) => { + const { activated_licenses, ...rest } = consumer + + let lastLicense: any = null + + if (consumer.activated_licenses.length) { + lastLicense = consumer.activated_licenses[0] + if (consumer.activated_licenses.length > 1) { + const activeLicenseInfo = consumer.activated_licenses.find( + activated_license => activated_license.expires_at, + ) + + if (!activeLicenseInfo) { + consumer.activated_licenses.sort((a, b) => (a.expires_at > b.expires_at ? 1 : -1)) + lastLicense = consumer.activated_licenses[0] + } + } + } + + return { + ...rest, + fullname: `${rest?.first_name} ${rest?.last_name}`, + license_info: prepareLicenseInfo(lastLicense), + } +} + +function prepareLicenseInfo(latestLicense: any) { + if (!latestLicense) return null + const { license, ...rest } = latestLicense + + return { + ...rest, + partner: license.charged_license_transaction.partner, + } +} diff --git a/src/modules/partners/partners.middleware.ts b/src/modules/partners/partners.middleware.ts index 69fecda..e850ec3 100644 --- a/src/modules/partners/partners.middleware.ts +++ b/src/modules/partners/partners.middleware.ts @@ -18,8 +18,6 @@ export class PartnerMiddleware implements NestMiddleware { async use(req: Request, _res: Response, next: NextFunction) { const doForbidden = () => { - console.log('doForbidden') - throw new ForbiddenException('شما دسترسی لازم را ندارید') } try { diff --git a/src/modules/pos/goods/goods.service.ts b/src/modules/pos/goods/goods.service.ts index 1324f89..d4bfedb 100644 --- a/src/modules/pos/goods/goods.service.ts +++ b/src/modules/pos/goods/goods.service.ts @@ -19,6 +19,7 @@ export class GoodsService { is_default_guild_good: true, pricing_model: true, unit_type: true, + image_url: true, category: { select: { id: true, diff --git a/src/modules/pos/pos.controller.ts b/src/modules/pos/pos.controller.ts index e678d5c..4af22f5 100644 --- a/src/modules/pos/pos.controller.ts +++ b/src/modules/pos/pos.controller.ts @@ -14,8 +14,6 @@ export class PosController { const result = await this.service.getInfo(pos_id) if (result) { - console.log('first') - // @ts-ignore res.cookie('posId', pos_id, { httpOnly: false, diff --git a/src/modules/storage/storage.service.ts b/src/modules/storage/storage.service.ts index 436f02a..424f4fb 100644 --- a/src/modules/storage/storage.service.ts +++ b/src/modules/storage/storage.service.ts @@ -38,7 +38,6 @@ export class StorageService { return publicUrl }) .catch(ex => { - console.log(ex) throw ex }) @@ -46,7 +45,7 @@ export class StorageService { } async deleteFile(key: string) { - await this.s3.send( + return await this.s3.send( new DeleteObjectCommand({ Bucket: this.bucket, Key: key, diff --git a/src/modules/uploader/uploader.service.ts b/src/modules/uploader/uploader.service.ts index a6de584..6d5437f 100644 --- a/src/modules/uploader/uploader.service.ts +++ b/src/modules/uploader/uploader.service.ts @@ -9,25 +9,14 @@ export class UploaderService { async uploadFile(file: Express.Multer.File, type: UploadedFileTypes) { const uploaded = await this.storageService.uploadFile(file, type) return uploaded - - // await fs.mkdir(dirPath, { recursive: true }) - // await fs.writeFile(filePath, buffer) - - // Return a URL path, e.g. /uploads/{accountId}/{type}/{filename} - // return `/uploads/${type}/${filename}` } - async deleteFile(url: string) { - const key = url?.split('/').pop() + async deleteFile(url: string, type: UploadedFileTypes) { + const key = `${type}/${url?.split('/').pop()}` + if (key) { return await this.storageService.deleteFile(key) } return {} - - // await fs.mkdir(dirPath, { recursive: true }) - // await fs.writeFile(filePath, buffer) - - // Return a URL path, e.g. /uploads/{accountId}/{type}/{filename} - // return `/uploads/${type}/${filename}` } }