init
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'
|
||||
import { ApiTags } from '@nestjs/swagger'
|
||||
import { ResponseMapper } from '../common/response/response-mapper'
|
||||
import { AuthService } from './auth.service'
|
||||
import { CurrentUser } from './current-user.decorator'
|
||||
import { RefreshTokenDto } from './dto/refresh-token.dto'
|
||||
import { SendCodeDto } from './dto/send-code.dto'
|
||||
import { VerifyCodeDto } from './dto/verify-code.dto'
|
||||
import { JwtAuthGuard } from './jwt-auth.guard'
|
||||
|
||||
@ApiTags('auth')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly auth: AuthService) {}
|
||||
|
||||
@Post('send-code')
|
||||
async sendCode(@Body() dto: SendCodeDto) {
|
||||
return this.auth.sendCode(dto)
|
||||
}
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() dto: VerifyCodeDto) {
|
||||
return this.auth.loginWithCode(dto)
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
async refresh(@Body() dto: RefreshTokenDto) {
|
||||
return this.auth.refreshToken(dto)
|
||||
}
|
||||
|
||||
@Post('logout')
|
||||
async logout(@Body() dto: RefreshTokenDto) {
|
||||
return this.auth.logout(dto.refreshToken)
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async me(@CurrentUser() user: any) {
|
||||
return ResponseMapper.single(user)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { JwtModule } from '@nestjs/jwt'
|
||||
import 'dotenv/config'
|
||||
import { env } from 'prisma/config'
|
||||
import { PrismaModule } from '../prisma/prisma.module'
|
||||
import { AuthController } from './auth.controller'
|
||||
import { AuthService } from './auth.service'
|
||||
import { JwtAuthGuard } from './jwt-auth.guard'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PrismaModule,
|
||||
JwtModule.register({
|
||||
secret: env('JWT_SECRET') || 'secret',
|
||||
signOptions: { expiresIn: Number(env('JWT_EXPIRES_IN')) || '15m' },
|
||||
}),
|
||||
],
|
||||
providers: [AuthService, JwtAuthGuard],
|
||||
controllers: [AuthController],
|
||||
exports: [AuthService, JwtAuthGuard],
|
||||
})
|
||||
export class AuthModule {}
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { createHash, randomBytes } from 'crypto'
|
||||
import 'dotenv/config'
|
||||
import { env } from 'prisma/config'
|
||||
import { ResponseMapper } from '../common/response/response-mapper'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import { RefreshTokenDto } from './dto/refresh-token.dto'
|
||||
import { SendCodeDto } from './dto/send-code.dto'
|
||||
import { VerifyCodeDto } from './dto/verify-code.dto'
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly OTP_TTL_MINUTES = 2
|
||||
private readonly ACCESS_TOKEN_EXP = Number(env('JWT_EXPIRES_IN')) || '15m'
|
||||
private readonly REFRESH_TOKEN_EXP_DAYS = 30
|
||||
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private jwt: JwtService,
|
||||
) {}
|
||||
|
||||
private generateCode() {
|
||||
// For development you may want fixed code via env, otherwise random 5-digit
|
||||
const staticCode = process.env.OTP_STATIC_CODE
|
||||
if (staticCode) return staticCode
|
||||
return Math.floor(10000 + Math.random() * 90000).toString()
|
||||
}
|
||||
|
||||
async sendCode(dto: SendCodeDto) {
|
||||
const { mobileNumber } = dto
|
||||
const user = await this.prisma.user.findFirst({ where: { mobileNumber } })
|
||||
if (!user) {
|
||||
throw new NotFoundException('کاربری با این شماره تماس یافت نشد')
|
||||
}
|
||||
const code = this.generateCode()
|
||||
const expiresAt = new Date(Date.now() + this.OTP_TTL_MINUTES * 60 * 1000)
|
||||
|
||||
await this.prisma.otpCode.create({
|
||||
data: {
|
||||
mobileNumber,
|
||||
code,
|
||||
expiresAt,
|
||||
},
|
||||
})
|
||||
|
||||
// TODO: integrate SMS provider. For now return the code for dev.
|
||||
return {
|
||||
ok: true,
|
||||
}
|
||||
}
|
||||
|
||||
async loginWithCode(dto: VerifyCodeDto) {
|
||||
const { mobileNumber, code } = dto
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: { mobileNumber },
|
||||
include: {
|
||||
role: true,
|
||||
},
|
||||
})
|
||||
if (!user) {
|
||||
throw new NotFoundException('کاربری با این شماره تماس یافت نشد')
|
||||
}
|
||||
|
||||
const otp = await this.prisma.otpCode.findFirst({
|
||||
where: { mobileNumber, used: false },
|
||||
})
|
||||
|
||||
if (!otp) throw new BadRequestException('اطلاعات ورودی درست نیست')
|
||||
if (otp.used) throw new BadRequestException('کد قبلن استفاده شده است')
|
||||
if (otp.expiresAt.getTime() < Date.now())
|
||||
throw new BadRequestException('کد وارد شده منقضی شده است')
|
||||
if (otp.code !== code) throw new BadRequestException('کد وارد شده اشتباه است')
|
||||
|
||||
// mark used
|
||||
await this.prisma.otpCode.update({ where: { id: otp.id }, data: { used: true } })
|
||||
|
||||
const accessToken = this.jwt.sign(
|
||||
{ sub: user.id, mobileNumber },
|
||||
{ expiresIn: this.ACCESS_TOKEN_EXP },
|
||||
)
|
||||
const refreshTokenPlain = randomBytes(48).toString('hex')
|
||||
const tokenHash = createHash('sha256').update(refreshTokenPlain).digest('hex')
|
||||
const expiresAt = new Date(
|
||||
Date.now() + this.REFRESH_TOKEN_EXP_DAYS * 24 * 60 * 60 * 1000,
|
||||
)
|
||||
|
||||
await this.prisma.refreshToken.create({
|
||||
data: { tokenHash, userId: user.id, expiresAt },
|
||||
})
|
||||
|
||||
return ResponseMapper.single({ accessToken, refreshToken: refreshTokenPlain, user })
|
||||
}
|
||||
|
||||
async refreshToken(dto: RefreshTokenDto) {
|
||||
const { refreshToken } = dto
|
||||
const tokenHash = createHash('sha256').update(refreshToken).digest('hex')
|
||||
const record = await this.prisma.refreshToken.findFirst({ where: { tokenHash } })
|
||||
if (!record || record.revoked || record.expiresAt.getTime() < Date.now()) {
|
||||
throw new UnauthorizedException('Invalid refresh token')
|
||||
}
|
||||
|
||||
const user = await this.prisma.user.findUnique({ where: { id: record.userId } })
|
||||
if (!user) throw new UnauthorizedException('User not found')
|
||||
|
||||
const accessToken = this.jwt.sign(
|
||||
{ sub: user.id, mobileNumber: user.mobileNumber },
|
||||
{ expiresIn: this.ACCESS_TOKEN_EXP },
|
||||
)
|
||||
return { accessToken }
|
||||
}
|
||||
|
||||
async logout(refreshToken: string) {
|
||||
const tokenHash = createHash('sha256').update(refreshToken).digest('hex')
|
||||
await this.prisma.refreshToken.updateMany({
|
||||
where: { tokenHash },
|
||||
data: { revoked: true },
|
||||
})
|
||||
return { ok: true }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
|
||||
|
||||
export const CurrentUser = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const req = ctx.switchToHttp().getRequest()
|
||||
return req.user
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsString } from 'class-validator'
|
||||
|
||||
export class LoginDto {
|
||||
@ApiProperty({ description: 'Mobile number used as username', example: '09123456789' })
|
||||
@IsString()
|
||||
mobileNumber: string
|
||||
|
||||
@ApiProperty({ description: 'OTP password (one-time password)', example: '12345' })
|
||||
@IsString()
|
||||
password: string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsString } from 'class-validator'
|
||||
|
||||
export class RefreshTokenDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
refreshToken: string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsString } from 'class-validator'
|
||||
|
||||
export class SendCodeDto {
|
||||
@ApiProperty({ example: '09123456789' })
|
||||
@IsString()
|
||||
mobileNumber: string
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsString } from 'class-validator'
|
||||
|
||||
export class VerifyCodeDto {
|
||||
@ApiProperty({ example: '09123456789' })
|
||||
@IsString()
|
||||
mobileNumber: string
|
||||
|
||||
@ApiProperty({ example: '12345' })
|
||||
@IsString()
|
||||
code: string
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { Request } from 'express'
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard implements CanActivate {
|
||||
constructor(private jwt: JwtService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest<Request>()
|
||||
const auth = req.headers.authorization
|
||||
if (!auth) throw new UnauthorizedException('Missing Authorization header')
|
||||
|
||||
const parts = auth.split(' ')
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||
throw new UnauthorizedException('Invalid Authorization header format')
|
||||
}
|
||||
|
||||
const token = parts[1]
|
||||
try {
|
||||
const payload = this.jwt.verify(token, {
|
||||
secret: process.env.JWT_SECRET || 'secret',
|
||||
})
|
||||
;(req as any).user = payload
|
||||
return true
|
||||
} catch (err) {
|
||||
throw new UnauthorizedException('Invalid or expired token')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { BankAccountsService } from './bank-accounts.service'
|
||||
import { CreateBankAccountDto } from './dto/create-bank-account.dto'
|
||||
import { UpdateBankAccountDto } from './dto/update-bank-account.dto'
|
||||
|
||||
@Controller('bank-accounts')
|
||||
export class BankAccountsController {
|
||||
constructor(private readonly bankAccountsService: BankAccountsService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateBankAccountDto) {
|
||||
return this.bankAccountsService.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.bankAccountsService.findAll()
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.bankAccountsService.findOne(Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateBankAccountDto) {
|
||||
return this.bankAccountsService.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.bankAccountsService.remove(Number(id))
|
||||
}
|
||||
|
||||
@Get(':id/transactions')
|
||||
getTransactions(@Param('id') id: string) {
|
||||
return this.bankAccountsService.getTransactions(Number(id))
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { BankAccountsController } from './bank-accounts.controller'
|
||||
import { BankAccountsService } from './bank-accounts.service'
|
||||
import { BankAccountsWorkflow } from './bank-accounts.workflow'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [BankAccountsController],
|
||||
providers: [BankAccountsService, BankAccountsWorkflow],
|
||||
exports: [BankAccountsWorkflow],
|
||||
})
|
||||
export class BankAccountsModule {}
|
||||
@@ -1,153 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { BankTransactionRefType } from '../../generated/prisma/enums'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class BankAccountsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
private readonly bankAccountQuery = {
|
||||
include: {
|
||||
branch: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
code: true,
|
||||
bank: {
|
||||
select: { id: true, name: true, shortName: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
bankAccountTransactions: {
|
||||
take: 1,
|
||||
select: { id: true, createdAt: true, balanceAfter: true },
|
||||
orderBy: { createdAt: 'desc' as const },
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
branchId: true,
|
||||
},
|
||||
}
|
||||
|
||||
async create(data: any) {
|
||||
const item = await this.prisma.bankAccount.create({ data })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.bankAccount.findMany(this.bankAccountQuery)
|
||||
const mappedData = items.map(({ bankAccountTransactions, ...rest }) => ({
|
||||
...rest,
|
||||
currentBalance: Number(bankAccountTransactions[0]?.balanceAfter || 0),
|
||||
}))
|
||||
|
||||
return ResponseMapper.list(mappedData)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.bankAccount.findUniqueOrThrow({
|
||||
...this.bankAccountQuery,
|
||||
where: { id },
|
||||
})
|
||||
|
||||
const { bankAccountTransactions, ...rest } = item
|
||||
return ResponseMapper.single({
|
||||
...rest,
|
||||
currentBalance: Number(bankAccountTransactions[0]?.balanceAfter || 0),
|
||||
})
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const item = await this.prisma.bankAccount.update({ where: { id }, data })
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.bankAccount.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async getTransactions(bankAccountId: number, page = 1, pageSize = 50) {
|
||||
const query = {
|
||||
where: { bankAccountId },
|
||||
orderBy: { createdAt: 'desc' as const },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}
|
||||
|
||||
const [items, count] = await this.prisma.$transaction([
|
||||
this.prisma.bankAccountTransaction.findMany({
|
||||
...query,
|
||||
omit: { bankAccountId: true },
|
||||
}),
|
||||
this.prisma.bankAccountTransaction.count({ where: { bankAccountId } }),
|
||||
])
|
||||
|
||||
const PosReferenceIds = [] as number[]
|
||||
const purchaseReferenceIds = [] as number[]
|
||||
|
||||
items.forEach(({ referenceType, referenceId }) => {
|
||||
if (referenceId && referenceType) {
|
||||
if (
|
||||
referenceType === BankTransactionRefType.POS_SALE ||
|
||||
referenceType === BankTransactionRefType.POS_REFUND
|
||||
) {
|
||||
PosReferenceIds.push(referenceId)
|
||||
} else if (
|
||||
referenceType === BankTransactionRefType.PURCHASE_PAYMENT ||
|
||||
referenceType === BankTransactionRefType.PURCHASE_REFUND
|
||||
) {
|
||||
purchaseReferenceIds.push(referenceId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const posRecords = await this.prisma.salesInvoice.findMany({
|
||||
where: { id: { in: PosReferenceIds } },
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
totalAmount: true,
|
||||
posAccount: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
customer: {
|
||||
select: { id: true, firstName: true, lastName: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const purchaseRecords = await this.prisma.purchaseReceipt.findMany({
|
||||
where: { id: { in: purchaseReferenceIds } },
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
totalAmount: true,
|
||||
supplier: {
|
||||
select: { id: true, firstName: true, lastName: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const mappedItems = items.map(tx => {
|
||||
let reference = null as any
|
||||
if (tx.referenceId && tx.referenceType) {
|
||||
if (
|
||||
tx.referenceType === BankTransactionRefType.POS_SALE ||
|
||||
tx.referenceType === BankTransactionRefType.POS_REFUND
|
||||
) {
|
||||
reference = posRecords.find(r => r.id === tx.referenceId) || null
|
||||
} else if (
|
||||
tx.referenceType === BankTransactionRefType.PURCHASE_PAYMENT ||
|
||||
tx.referenceType === BankTransactionRefType.PURCHASE_REFUND
|
||||
) {
|
||||
reference = purchaseRecords.find(r => r.id === tx.referenceId) || null
|
||||
}
|
||||
}
|
||||
return { ...tx, reference }
|
||||
})
|
||||
|
||||
return ResponseMapper.paginate(mappedItems, count, page, pageSize)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Prisma } from '../../generated/prisma/client'
|
||||
import { AddTransactionToBankAccountDto } from './dto/add-transaction.dto'
|
||||
|
||||
@Injectable()
|
||||
export class BankAccountsWorkflow {
|
||||
async addTransaction(
|
||||
tx: Prisma.TransactionClient,
|
||||
payload: AddTransactionToBankAccountDto,
|
||||
) {
|
||||
const item = await tx.bankAccountTransaction.findFirst({
|
||||
where: {
|
||||
bankAccountId: payload.bankAccountId,
|
||||
},
|
||||
})
|
||||
|
||||
const balance = item ? Number(item?.balanceAfter) : 0
|
||||
|
||||
const newBalance = item
|
||||
? payload.type === 'DEPOSIT'
|
||||
? balance + payload.amount
|
||||
: balance - payload.amount
|
||||
: payload.type === 'DEPOSIT'
|
||||
? payload.amount
|
||||
: -payload.amount
|
||||
|
||||
await tx.bankAccountTransaction.create({
|
||||
data: {
|
||||
bankAccount: { connect: { id: payload.bankAccountId } },
|
||||
amount: payload.amount,
|
||||
type: payload.type,
|
||||
balanceAfter: newBalance,
|
||||
|
||||
referenceId: payload.referenceId,
|
||||
referenceType: payload.referenceType,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { IsEnum, IsInt } from 'class-validator'
|
||||
import {
|
||||
BankAccountTransactionType,
|
||||
BankTransactionRefType,
|
||||
} from '../../../generated/prisma/enums'
|
||||
|
||||
export class AddTransactionToBankAccountDto {
|
||||
@IsInt()
|
||||
bankAccountId: number
|
||||
|
||||
@IsInt()
|
||||
amount: number
|
||||
|
||||
@IsInt()
|
||||
referenceId: number
|
||||
|
||||
@IsEnum(BankTransactionRefType)
|
||||
referenceType: BankTransactionRefType
|
||||
|
||||
@IsEnum(BankAccountTransactionType)
|
||||
type: BankAccountTransactionType
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { IsInt, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class CreateBankAccountDto {
|
||||
@IsString()
|
||||
accountNumber: string
|
||||
|
||||
@IsString()
|
||||
name: string
|
||||
|
||||
@IsString()
|
||||
iban: string
|
||||
|
||||
@IsInt()
|
||||
branchId: number
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
createdAt?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
updatedAt?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
deletedAt?: string
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger'
|
||||
import { CreateBankAccountDto } from './create-bank-account.dto'
|
||||
|
||||
export class UpdateBankAccountDto extends PartialType(CreateBankAccountDto) {}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { BankBranchesService } from './bank-branches.service'
|
||||
import { CreateBankBranchDto } from './dto/create-bank-branches.dto'
|
||||
import { UpdateBankBranchDto } from './dto/update-bank-branches.dto'
|
||||
|
||||
@Controller('bank-branches')
|
||||
export class BankBranchesController {
|
||||
constructor(private readonly bankBranchesService: BankBranchesService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateBankBranchDto) {
|
||||
return this.bankBranchesService.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.bankBranchesService.findAll()
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.bankBranchesService.findOne(Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateBankBranchDto) {
|
||||
return this.bankBranchesService.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.bankBranchesService.remove(Number(id))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { BankBranchesController } from './bank-branches.controller'
|
||||
import { BankBranchesService } from './bank-branches.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [BankBranchesController],
|
||||
providers: [BankBranchesService],
|
||||
})
|
||||
export class BankBranchesModule {}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { IsInt, IsString } from 'class-validator'
|
||||
|
||||
export class CreateBankBranchDto {
|
||||
@IsString()
|
||||
name: string
|
||||
|
||||
@IsString()
|
||||
code: string
|
||||
|
||||
@IsInt()
|
||||
bankId: number
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Controller, Get } from '@nestjs/common'
|
||||
import { BanksService } from './banks.service'
|
||||
|
||||
@Controller('banks')
|
||||
export class BanksController {
|
||||
constructor(private readonly banksService: BanksService) {}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.banksService.findAll()
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { BanksController } from './banks.controller'
|
||||
import { BanksService } from './banks.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [BanksController],
|
||||
providers: [BanksService],
|
||||
})
|
||||
export class BanksModule {}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class BanksService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.bank.findMany({
|
||||
select: { id: true, name: true, shortName: true },
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common'
|
||||
import { ApiQuery } from '@nestjs/swagger'
|
||||
import { CardexService } from './cardex.service'
|
||||
|
||||
@Controller('cardex')
|
||||
export class CardexController {
|
||||
constructor(private readonly service: CardexService) {}
|
||||
|
||||
@Get('')
|
||||
@ApiQuery({ name: 'startDate', required: true, type: Date })
|
||||
@ApiQuery({ name: 'endDate', required: false, type: Date })
|
||||
@ApiQuery({ name: 'inventoryId', required: false, type: Number })
|
||||
@ApiQuery({ name: 'productId', required: false, type: Number })
|
||||
async getStock(
|
||||
@Query('startDate') startDate: Date,
|
||||
@Query('endDate') endDate: Date,
|
||||
@Query('inventoryId') inventoryId: number,
|
||||
@Query('productId') productId: number,
|
||||
) {
|
||||
return this.service.getCardex({
|
||||
startDate,
|
||||
endDate,
|
||||
inventoryId,
|
||||
productId,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { CardexController } from './cardex.controller'
|
||||
import { CardexService } from './cardex.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [CardexController],
|
||||
providers: [CardexService],
|
||||
})
|
||||
export class CardexModule {}
|
||||
@@ -1,54 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import dayjs from 'dayjs'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { Prisma } from '../../generated/prisma/client'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
import { ReadCardexDto } from './dto/read-cardex.dto'
|
||||
|
||||
@Injectable()
|
||||
export class CardexService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getCardex({ startDate, endDate, inventoryId, productId }: ReadCardexDto) {
|
||||
if (!endDate) {
|
||||
endDate = dayjs(startDate).add(1, 'months').toDate()
|
||||
}
|
||||
const where: Prisma.StockMovementWhereInput = {}
|
||||
|
||||
if (inventoryId !== undefined) where.inventoryId = inventoryId
|
||||
if (productId !== undefined) where.productId = productId
|
||||
|
||||
if (startDate || endDate) {
|
||||
where.createdAt = {}
|
||||
if (startDate) where.createdAt.gte = new Date(startDate)
|
||||
if (endDate) where.createdAt.lte = new Date(endDate)
|
||||
}
|
||||
|
||||
const items = await this.prisma.stockMovement.findMany({
|
||||
where,
|
||||
include: {
|
||||
product: { select: { id: true, name: true, sku: true } },
|
||||
inventory: { select: { id: true, name: true } },
|
||||
counterInventory: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
supplier: {
|
||||
select: { id: true, firstName: true, lastName: true },
|
||||
},
|
||||
customer: {
|
||||
select: { id: true, firstName: true, lastName: true },
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
productId: true,
|
||||
inventoryId: true,
|
||||
counterInventoryId: true,
|
||||
supplierId: true,
|
||||
customerId: true,
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger'
|
||||
import { IsDateString, IsNumber, IsOptional } from 'class-validator'
|
||||
|
||||
export class ReadCardexDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsDateString()
|
||||
startDate: Date
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
endDate?: Date
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
inventoryId?: number
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
productId?: number
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { CustomersService } from './customers.service'
|
||||
import { CreateCustomerDto } from './dto/create-customer.dto'
|
||||
import { UpdateCustomerDto } from './dto/update-customer.dto'
|
||||
|
||||
@Controller('customers')
|
||||
export class CustomersController {
|
||||
constructor(private readonly customersService: CustomersService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateCustomerDto) {
|
||||
return this.customersService.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.customersService.findAll()
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.customersService.findOne(Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateCustomerDto) {
|
||||
return this.customersService.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.customersService.remove(Number(id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { CustomersController } from './customers.controller'
|
||||
import { CustomersService } from './customers.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [CustomersController],
|
||||
providers: [CustomersService],
|
||||
})
|
||||
export class CustomersModule {}
|
||||
+6
-12
@@ -3,37 +3,31 @@ import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class BankBranchesService {
|
||||
export class CustomersService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
async create(data: any) {
|
||||
const item = await this.prisma.bankBranch.create({ data })
|
||||
const item = await this.prisma.customer.create({ data })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.bankBranch.findMany({
|
||||
include: {
|
||||
bank: {
|
||||
select: { id: true, name: true, shortName: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
const items = await this.prisma.customer.findMany()
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.bankBranch.findUnique({ where: { id } })
|
||||
const item = await this.prisma.customer.findUnique({ where: { id } })
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const item = await this.prisma.bankBranch.update({ where: { id }, data })
|
||||
const item = await this.prisma.customer.update({ where: { id }, data })
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.bankBranch.delete({ where: { id } })
|
||||
const item = await this.prisma.customer.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { IsEmail, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class CreateSupplierDto {
|
||||
export class CreateCustomerDto {
|
||||
@IsString()
|
||||
firstName: string
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { Type } from 'class-transformer'
|
||||
import { IsBoolean, IsEmail, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class UpdateSupplierDto {
|
||||
export class UpdateCustomerDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
firstName?: string
|
||||
@@ -1,6 +0,0 @@
|
||||
import { IsNumber } from 'class-validator'
|
||||
|
||||
export class UpdateInventoryBankAccountDto {
|
||||
@IsNumber()
|
||||
bankAccountId: number
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
|
||||
import { CreateBankAccountDto } from '../../bank-accounts/dto/create-bank-account.dto'
|
||||
import { UpdateInventoryBankAccountDto } from './dto/update-inventory-bank-account.dto'
|
||||
import { InventoryBankAccountsService } from './inventory-bank-accounts.service'
|
||||
|
||||
@Controller('inventories/:inventoryId/bank-accounts')
|
||||
export class InventoryBankAccountsController {
|
||||
constructor(private readonly bankAccountsService: InventoryBankAccountsService) {}
|
||||
|
||||
@Post()
|
||||
create(
|
||||
@Param('inventoryId') inventoryId: string,
|
||||
@Body() bankAccount: CreateBankAccountDto,
|
||||
) {
|
||||
return this.bankAccountsService.create(Number(inventoryId), bankAccount)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Param('inventoryId') inventoryId: string) {
|
||||
return this.bankAccountsService.findAll(Number(inventoryId))
|
||||
}
|
||||
|
||||
@Post('/assign')
|
||||
assign(
|
||||
@Param('inventoryId') inventoryId: string,
|
||||
@Body() dto: UpdateInventoryBankAccountDto,
|
||||
) {
|
||||
return this.bankAccountsService.assign(Number(inventoryId), dto.bankAccountId)
|
||||
}
|
||||
|
||||
@Post('/unassign')
|
||||
unassign(
|
||||
@Param('inventoryId') inventoryId: string,
|
||||
@Body() dto: UpdateInventoryBankAccountDto,
|
||||
) {
|
||||
return this.bankAccountsService.unassign(Number(inventoryId), dto.bankAccountId)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../../prisma/prisma.module'
|
||||
import { InventoryBankAccountsController } from './inventory-bank-accounts.controller'
|
||||
import { InventoryBankAccountsService } from './inventory-bank-accounts.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [InventoryBankAccountsController],
|
||||
providers: [InventoryBankAccountsService],
|
||||
})
|
||||
export class InventoryBankAccountsModule {}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
import { CreateBankAccountDto } from '../../bank-accounts/dto/create-bank-account.dto'
|
||||
|
||||
@Injectable()
|
||||
export class InventoryBankAccountsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create(inventoryId: number, bankAccount: CreateBankAccountDto) {
|
||||
return this.prisma.bankAccount
|
||||
.create({
|
||||
data: bankAccount,
|
||||
})
|
||||
.then(async res => {
|
||||
await this.prisma.inventoryBankAccount.create({
|
||||
data: {
|
||||
inventoryId,
|
||||
bankAccountId: res.id,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.create(res)
|
||||
})
|
||||
}
|
||||
|
||||
async findAll(inventoryId: number) {
|
||||
const items = await this.prisma.inventoryBankAccount.findMany({
|
||||
where: {
|
||||
inventoryId,
|
||||
},
|
||||
include: {
|
||||
bankAccount: {
|
||||
include: {
|
||||
branch: {
|
||||
include: {
|
||||
bank: {
|
||||
select: { id: true, name: true, shortName: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
posAccounts: {
|
||||
select: { id: true, name: true, code: true },
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
bankAccountId: true,
|
||||
inventoryId: true,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.list(
|
||||
items.map(item => ({ posAccounts: item.posAccounts, ...item.bankAccount })),
|
||||
)
|
||||
}
|
||||
|
||||
async assign(inventoryId: number, bankAccountId: number) {
|
||||
const item = await this.prisma.inventoryBankAccount.create({
|
||||
data: {
|
||||
inventoryId,
|
||||
bankAccountId,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async unassign(inventoryId: number, bankAccountId: number) {
|
||||
const item = await this.prisma.inventoryBankAccount.deleteMany({
|
||||
where: {
|
||||
inventoryId,
|
||||
bankAccountId,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||
import { IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class CreateInventoryDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
location?: string
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger'
|
||||
import { Type } from 'class-transformer'
|
||||
import { IsBoolean, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class UpdateInventoryDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
location?: string
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@Type(() => Boolean)
|
||||
@IsBoolean()
|
||||
isActive?: boolean
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'
|
||||
import { ApiQuery } from '@nestjs/swagger'
|
||||
import { MovementType } from '../../../generated/prisma/enums'
|
||||
import { CreateInventoryDto } from './dto/create-inventory.dto'
|
||||
import { UpdateInventoryDto } from './dto/update-inventory.dto'
|
||||
import { InventoriesService } from './inventories.service'
|
||||
|
||||
@Controller('inventories')
|
||||
export class InventoriesController {
|
||||
constructor(private readonly inventoriesService: InventoriesService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateInventoryDto) {
|
||||
return this.inventoriesService.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiQuery({ name: 'isPointOfSale', required: false, type: Boolean })
|
||||
findAll(@Query('isPointOfSale') isPointOfSale?: boolean) {
|
||||
return this.inventoriesService.findAll(isPointOfSale)
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.inventoriesService.findOne(Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateInventoryDto) {
|
||||
return this.inventoriesService.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.inventoriesService.remove(Number(id))
|
||||
}
|
||||
|
||||
@Get(':id/movements')
|
||||
@ApiQuery({ name: 'type', required: false, enum: MovementType })
|
||||
findInventoryMovements(@Param('id') id: string, @Query('type') type?: MovementType) {
|
||||
return this.inventoriesService.findInventoryMovements(Number(id), type ?? undefined)
|
||||
}
|
||||
|
||||
@Get(':id/stock')
|
||||
@ApiQuery({ name: 'isAvailable', required: false, type: Boolean })
|
||||
getStock(@Param('id') id: string, @Query('isAvailable') isAvailable?: boolean) {
|
||||
return this.inventoriesService.getStock(Number(id), isAvailable ?? undefined)
|
||||
}
|
||||
|
||||
@Get(':id/products/:productId/cardex')
|
||||
getProductCardex(@Param('id') id: string, @Param('productId') productId: string) {
|
||||
return this.inventoriesService.getProductCardex(Number(id), Number(productId))
|
||||
}
|
||||
|
||||
@Get(':id/cardex')
|
||||
getCardex(@Param('id') id: string) {
|
||||
return this.inventoriesService.getCardex(Number(id))
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { MovementType } from '../../../generated/prisma/enums'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class InventoriesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
async create(data: any) {
|
||||
const item = await this.prisma.inventory.create({ data })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async findAll(isPointOfSale?: boolean) {
|
||||
const items = await this.prisma.inventory.findMany({
|
||||
where: isPointOfSale !== undefined ? { isPointOfSale } : undefined,
|
||||
})
|
||||
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.inventory.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
stockBalances: {
|
||||
where: { quantity: { gt: 0 } },
|
||||
select: { quantity: true, totalCost: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!item) return null
|
||||
|
||||
const { stockBalances, ...rest } = item
|
||||
return ResponseMapper.single({
|
||||
...rest,
|
||||
availableProductTypes: stockBalances.length,
|
||||
availableProductCount: stockBalances.reduce(
|
||||
(acc, sb) => acc + Number(sb.quantity),
|
||||
0,
|
||||
),
|
||||
availableProductsCost: stockBalances.reduce(
|
||||
(acc, sb) => acc + Number(sb.totalCost),
|
||||
0,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const item = await this.prisma.inventory.update({ where: { id }, data })
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.inventory.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async findInventoryMovements(
|
||||
inventoryId: number,
|
||||
type?: MovementType,
|
||||
page = 1,
|
||||
pageSize = 10,
|
||||
) {
|
||||
const groups = await this.prisma.stockMovement.groupBy({
|
||||
by: ['referenceId', 'referenceType'],
|
||||
where: { inventoryId, type },
|
||||
_count: { id: true },
|
||||
orderBy: { referenceId: 'desc' },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
const result = await Promise.all(
|
||||
groups.map(async group => {
|
||||
const movements = await this.prisma.stockMovement.findMany({
|
||||
where: {
|
||||
inventoryId,
|
||||
AND: {
|
||||
referenceId: group.referenceId,
|
||||
AND: { referenceType: group.referenceType },
|
||||
},
|
||||
},
|
||||
include: {
|
||||
product: true,
|
||||
counterInventory: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
let info = null as any
|
||||
|
||||
const mapped = movements.map(movement => {
|
||||
const { id, quantity, unitPrice, totalCost, avgCost, product } = movement
|
||||
if (info === null) {
|
||||
info = {
|
||||
date: movement.createdAt,
|
||||
type: movement.type,
|
||||
quantity: Number(movement.quantity),
|
||||
unitPrice: Number(movement.unitPrice),
|
||||
totalCost: Number(movement.totalCost),
|
||||
referenceType: movement.referenceType,
|
||||
referenceId: movement.referenceId,
|
||||
counterInventory: movement.counterInventory,
|
||||
createdAt: movement.createdAt,
|
||||
}
|
||||
} else {
|
||||
info.quantity += Number(movement.quantity)
|
||||
info.unitPrice += Number(movement.unitPrice)
|
||||
info.totalCost += Number(movement.totalCost)
|
||||
}
|
||||
return {
|
||||
id,
|
||||
quantity: Number(quantity),
|
||||
unitPrice: Number(unitPrice),
|
||||
totalCost: Number(totalCost),
|
||||
avgCost: Number(avgCost),
|
||||
product,
|
||||
}
|
||||
})
|
||||
return {
|
||||
receiptId: group.referenceId,
|
||||
count: group._count.id,
|
||||
info,
|
||||
movements: mapped,
|
||||
}
|
||||
}),
|
||||
)
|
||||
return ResponseMapper.list(result)
|
||||
}
|
||||
|
||||
async getStock(inventoryId: number, isAvailable?: boolean, page = 1, pageSize = 10) {
|
||||
const items = await this.prisma.stockBalance.findMany({
|
||||
where: {
|
||||
inventoryId,
|
||||
quantity:
|
||||
isAvailable === true
|
||||
? { gt: 0 }
|
||||
: isAvailable === false
|
||||
? { lte: 0 }
|
||||
: undefined,
|
||||
},
|
||||
include: {
|
||||
product: true,
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: 'desc',
|
||||
},
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
const mapped = items.map(item => ({
|
||||
id: item.id,
|
||||
quantity: Number(item.quantity),
|
||||
avgCost: Number(item.avgCost),
|
||||
product: item.product,
|
||||
}))
|
||||
|
||||
return ResponseMapper.list(mapped)
|
||||
}
|
||||
|
||||
async getCardex(inventoryId: number) {
|
||||
const movements = await this.prisma.stockMovement.findMany({
|
||||
where: { inventoryId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
include: {
|
||||
customer: { select: { id: true, firstName: true, lastName: true } },
|
||||
supplier: { select: { id: true, firstName: true, lastName: true } },
|
||||
counterInventory: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
product: {
|
||||
select: { id: true, name: true, sku: true },
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
productId: true,
|
||||
customerId: true,
|
||||
supplierId: true,
|
||||
counterInventoryId: true,
|
||||
},
|
||||
})
|
||||
|
||||
return ResponseMapper.list(movements)
|
||||
}
|
||||
|
||||
async getProductCardex(inventoryId: number, productId: number) {
|
||||
const movements = await this.prisma.stockMovement.findMany({
|
||||
where: { productId, inventoryId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
include: {
|
||||
customer: { select: { id: true, firstName: true, lastName: true } },
|
||||
supplier: { select: { id: true, firstName: true, lastName: true } },
|
||||
counterInventory: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return ResponseMapper.list(movements)
|
||||
}
|
||||
|
||||
async getInventoryBankAccounts(inventoryId: number) {
|
||||
const items = await this.prisma.bankAccount.findMany({
|
||||
where: { inventoryBankAccounts: { some: { inventoryId } } },
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface IInventoryResponse {
|
||||
name: string
|
||||
location?: string
|
||||
isActive: boolean
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { InventoryBankAccountsModule } from './bank-accounts/inventory-bank-accounts.module'
|
||||
import { InventoriesController } from './index/inventories.controller'
|
||||
import { InventoriesService } from './index/inventories.service'
|
||||
import { PosAccountsModule } from './pos-accounts/pos-accounts.module'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, InventoryBankAccountsModule, PosAccountsModule],
|
||||
controllers: [InventoriesController],
|
||||
providers: [InventoriesService],
|
||||
})
|
||||
export class InventoriesModule {}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { IsInt, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class CreatePosAccountDto {
|
||||
@IsString()
|
||||
name: string
|
||||
|
||||
@IsString()
|
||||
code: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@IsInt()
|
||||
bankAccountId: number
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger'
|
||||
import { IsInt } from 'class-validator'
|
||||
import { CreatePosAccountDto } from './create-pos-accounts.dto'
|
||||
|
||||
export class UpdatePosAccountDto extends PartialType(CreatePosAccountDto) {
|
||||
@IsInt()
|
||||
bankAccountId: number
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { CreatePosAccountDto } from './dto/create-pos-accounts.dto'
|
||||
import { UpdatePosAccountDto } from './dto/update-pos-accounts.dto'
|
||||
import { PosAccountsService } from './pos-accounts.service'
|
||||
|
||||
@Controller('inventories/:inventoryId/pos-accounts')
|
||||
export class PosAccountsController {
|
||||
constructor(private readonly posAccountsService: PosAccountsService) {}
|
||||
|
||||
@Post()
|
||||
create(
|
||||
@Param('inventoryId') inventoryId: string,
|
||||
@Body() createPosAccountDto: CreatePosAccountDto,
|
||||
) {
|
||||
return this.posAccountsService.create(Number(inventoryId), createPosAccountDto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Param('inventoryId') inventoryId: string) {
|
||||
return this.posAccountsService.findAll(Number(inventoryId))
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('inventoryId') inventoryId: string, @Param('id') id: string) {
|
||||
return this.posAccountsService.findOne(Number(inventoryId), Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(
|
||||
@Param('inventoryId') inventoryId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() updatePosAccountDto: UpdatePosAccountDto,
|
||||
) {
|
||||
return this.posAccountsService.update(
|
||||
Number(inventoryId),
|
||||
Number(id),
|
||||
updatePosAccountDto,
|
||||
)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('inventoryId') inventoryId: string, @Param('id') id: string) {
|
||||
return this.posAccountsService.remove(Number(inventoryId), Number(id))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../../prisma/prisma.module'
|
||||
import { PosAccountsController } from './pos-accounts.controller'
|
||||
import { PosAccountsService } from './pos-accounts.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [PosAccountsController],
|
||||
providers: [PosAccountsService],
|
||||
})
|
||||
export class PosAccountsModule {}
|
||||
@@ -1,157 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
import { CreatePosAccountDto } from './dto/create-pos-accounts.dto'
|
||||
import { UpdatePosAccountDto } from './dto/update-pos-accounts.dto'
|
||||
|
||||
@Injectable()
|
||||
export class PosAccountsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
private async validateBankAccountInventoryLink(
|
||||
inventoryId: number,
|
||||
bankAccountId: number,
|
||||
) {
|
||||
const link = await this.prisma.inventoryBankAccount.findUnique({
|
||||
where: {
|
||||
inventoryId_bankAccountId: {
|
||||
inventoryId,
|
||||
bankAccountId,
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!link) {
|
||||
throw new Error('حساب بانکی به این انبار متصل نیست')
|
||||
}
|
||||
}
|
||||
|
||||
async create(inventoryId: number, dto: CreatePosAccountDto) {
|
||||
if (dto.bankAccountId) {
|
||||
await this.validateBankAccountInventoryLink(inventoryId, dto.bankAccountId)
|
||||
}
|
||||
const item = await this.prisma.posAccount.create({
|
||||
data: {
|
||||
...dto,
|
||||
inventoryId,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async findAll(inventoryId: number) {
|
||||
const items = await this.prisma.posAccount.findMany({
|
||||
where: {
|
||||
inventoryId,
|
||||
},
|
||||
include: {
|
||||
inventoryBankAccount: {
|
||||
include: {
|
||||
bankAccount: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
accountNumber: true,
|
||||
branch: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
bank: { select: { id: true, name: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
salesInvoices: {
|
||||
where: {
|
||||
createdAt: { gte: new Date(new Date().setHours(0, 0, 0, 0)) },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
totalAmount: true,
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
bankAccountId: true,
|
||||
},
|
||||
})
|
||||
|
||||
return ResponseMapper.list(
|
||||
items.map(item => {
|
||||
const { inventoryBankAccount, salesInvoices, ...rest } = item
|
||||
return {
|
||||
...rest,
|
||||
bankAccount: inventoryBankAccount?.bankAccount,
|
||||
todaySalesInfo: {
|
||||
salesCount: salesInvoices.length,
|
||||
salesTotal: salesInvoices.reduce(
|
||||
(acc, si) => acc + Number(si.totalAmount),
|
||||
0,
|
||||
),
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async findOne(inventoryId: number, id: number) {
|
||||
const item = await this.prisma.posAccount.findFirst({
|
||||
where: {
|
||||
id,
|
||||
inventoryId,
|
||||
},
|
||||
include: {
|
||||
inventoryBankAccount: {
|
||||
include: {
|
||||
bankAccount: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
accountNumber: true,
|
||||
branch: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
bank: { select: { id: true, name: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
bankAccountId: true,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.single(
|
||||
item ? { ...item, bankAccount: item.inventoryBankAccount?.bankAccount } : null,
|
||||
)
|
||||
}
|
||||
|
||||
async update(inventoryId: number, id: number, dto: UpdatePosAccountDto) {
|
||||
await this.validateBankAccountInventoryLink(inventoryId, dto.bankAccountId)
|
||||
|
||||
const item = await this.prisma.posAccount.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: dto,
|
||||
})
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(inventoryId: number, id: number) {
|
||||
const item = await this.prisma.posAccount.delete({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.delete(item)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger'
|
||||
import { Type } from 'class-transformer'
|
||||
import { IsBoolean, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class UpdateInventoryDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
location?: string
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@Type(() => Boolean)
|
||||
@IsBoolean()
|
||||
isActive?: boolean
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||
import { Type } from 'class-transformer'
|
||||
import { ArrayMinSize, IsInt, IsNumber, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class CreateOrderDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
customerId?: number
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@ApiProperty()
|
||||
@ArrayMinSize(1)
|
||||
items: CreateOrderItemDto[]
|
||||
}
|
||||
|
||||
export class CreateOrderItemDto {
|
||||
@ApiProperty()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
productId: number
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
count: number
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
unitPrice: number
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
|
||||
import { OrderStatus } from '../../../generated/prisma/enums'
|
||||
import { CreateOrderDto } from './dto/create-order-item.dto'
|
||||
import { PosOrdersService } from './orders.service'
|
||||
|
||||
@Controller('pos/:posId/orders')
|
||||
export class PosOrdersController {
|
||||
constructor(private readonly posOrdersService: PosOrdersService) {}
|
||||
|
||||
@Get('')
|
||||
get(@Param('posId') posId: string) {
|
||||
return this.posOrdersService.getOrders(Number(posId))
|
||||
}
|
||||
|
||||
@Get('/held')
|
||||
getHeld(@Param('posId') posId: string) {
|
||||
return this.posOrdersService.getOrders(Number(posId), OrderStatus.PENDING)
|
||||
}
|
||||
|
||||
@Post('')
|
||||
async createOrder(@Param('posId') posId: string, @Body() dto: CreateOrderDto) {
|
||||
return this.posOrdersService.createOrder(Number(posId), dto)
|
||||
}
|
||||
|
||||
@Get('/:orderId')
|
||||
getOrderDetails(@Param('posId') posId: string, @Param('orderId') orderId: string) {
|
||||
return this.posOrdersService.getOrderDetails(Number(posId), Number(orderId))
|
||||
}
|
||||
|
||||
@Post('/:orderId/reject')
|
||||
async rejectOrder(@Param('posId') posId: string, @Param('orderId') orderId: string) {
|
||||
return this.posOrdersService.rejectOrder(Number(posId), Number(orderId))
|
||||
}
|
||||
|
||||
@Post('/:orderId/done')
|
||||
async completeOrder(@Param('posId') posId: string, @Param('orderId') orderId: string) {
|
||||
return this.posOrdersService.completeOrder(Number(posId), Number(orderId))
|
||||
}
|
||||
|
||||
@Post('/:orderId/cancel')
|
||||
async cancelOrder(@Param('posId') posId: string, @Param('orderId') orderId: string) {
|
||||
return this.posOrdersService.cancelOrder(Number(posId), Number(orderId))
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../../prisma/prisma.module'
|
||||
import { SalesInvoicesModule } from '../../sales-invoices/sales-invoices.module'
|
||||
import { PosOrdersController } from './orders.controller'
|
||||
import { PosOrdersService } from './orders.service'
|
||||
import { PosOrdersWorkflow } from './orders.workflow'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, SalesInvoicesModule],
|
||||
controllers: [PosOrdersController],
|
||||
providers: [PosOrdersService, PosOrdersWorkflow],
|
||||
})
|
||||
export class PosOrdersModule {}
|
||||
@@ -1,145 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { Prisma } from '../../../generated/prisma/client'
|
||||
import { OrderStatus } from '../../../generated/prisma/enums'
|
||||
import { OrderCreateInput } from '../../../generated/prisma/models'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
import { CreateOrderDto } from './dto/create-order-item.dto'
|
||||
import { PosOrdersWorkflow } from './orders.workflow'
|
||||
|
||||
@Injectable()
|
||||
export class PosOrdersService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private posWorkflow: PosOrdersWorkflow,
|
||||
) {}
|
||||
|
||||
async createOrder(posId: number, data: CreateOrderDto) {
|
||||
const { customerId, items, ...rest } = data
|
||||
const lastCode = await this.prisma.order
|
||||
.findFirst({
|
||||
where: { posAccountId: posId },
|
||||
orderBy: { orderNumber: 'desc' },
|
||||
select: { orderNumber: true },
|
||||
})
|
||||
.then(si => si?.orderNumber || '0')
|
||||
|
||||
const newCode = String(Number(lastCode) + 1)
|
||||
|
||||
const totalAmount = data.items.reduce(
|
||||
(acc, item) => acc + item.count * item.unitPrice,
|
||||
0,
|
||||
)
|
||||
|
||||
const preparedOrder: OrderCreateInput = {
|
||||
...rest,
|
||||
orderNumber: newCode,
|
||||
posAccount: { connect: { id: posId } },
|
||||
customer: customerId ? { connect: { id: customerId } } : undefined,
|
||||
totalAmount,
|
||||
orderItems: {
|
||||
create: items.map(item => ({
|
||||
product: { connect: { id: Number(item.productId) } },
|
||||
quantity: item.count,
|
||||
unitPrice: item.unitPrice,
|
||||
totalAmount: item.count * item.unitPrice,
|
||||
})),
|
||||
},
|
||||
}
|
||||
|
||||
const item = await this.prisma.$transaction(async tx => {
|
||||
const order = await tx.order.create({ data: preparedOrder })
|
||||
|
||||
await this.posWorkflow.onCreateOrder(tx, order.id, posId)
|
||||
})
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async createAndConfirmOrder(posId: number, data: CreateOrderDto) {}
|
||||
|
||||
async rejectOrder(posAccountId: number, orderId: number) {
|
||||
const item = await this.prisma.$transaction(async tx => {
|
||||
const order = await tx.order.update({
|
||||
where: { id: orderId, posAccountId },
|
||||
data: { status: OrderStatus.REJECTED },
|
||||
})
|
||||
await this.posWorkflow.onRejectOrder(tx, orderId)
|
||||
return order
|
||||
})
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async completeOrder(posAccountId: number, orderId: number) {
|
||||
const item = await this.prisma.$transaction(async tx => {
|
||||
const order = await tx.order.update({
|
||||
where: { id: orderId, posAccountId },
|
||||
data: { status: OrderStatus.DONE },
|
||||
})
|
||||
await this.posWorkflow.onConfirmOrder(tx, orderId)
|
||||
return order
|
||||
})
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async cancelOrder(posAccountId: number, orderId: number) {
|
||||
const item = await this.prisma.$transaction(async tx => {
|
||||
const order = await tx.order.update({
|
||||
where: { id: orderId, posAccountId },
|
||||
data: { status: OrderStatus.CANCELED },
|
||||
})
|
||||
await this.posWorkflow.onCancelOrder(tx, orderId)
|
||||
return order
|
||||
})
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async getOrders(posId: number, status?: OrderStatus) {
|
||||
const where: Prisma.OrderWhereInput = {
|
||||
posAccountId: posId,
|
||||
status,
|
||||
}
|
||||
if (status) {
|
||||
where.status = status
|
||||
}
|
||||
const items = await this.prisma.order.findMany({
|
||||
where,
|
||||
include: {
|
||||
customer: {
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
orderItems: {
|
||||
include: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async getOrderDetails(posAccountId: number, orderId: number) {
|
||||
const item = await this.prisma.order.findUniqueOrThrow({
|
||||
where: { id: orderId, posAccountId },
|
||||
include: {
|
||||
customer: {
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
orderItems: {
|
||||
include: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Prisma } from '../../../generated/prisma/client'
|
||||
import { SalesInvoicesWorkflow } from '../../sales-invoices/sales-invoices.workflow'
|
||||
|
||||
@Injectable()
|
||||
export class PosOrdersWorkflow {
|
||||
constructor(private salesInvoicesWorkflow: SalesInvoicesWorkflow) {}
|
||||
|
||||
async onCreateOrder(tx: Prisma.TransactionClient, orderId: number, posId: number) {
|
||||
const { inventoryId } = await tx.posAccount.findUniqueOrThrow({
|
||||
where: { id: posId },
|
||||
select: { inventoryId: true },
|
||||
})
|
||||
|
||||
const orderItems = await tx.orderItem.findMany({
|
||||
where: { orderId },
|
||||
select: {
|
||||
product: true,
|
||||
quantity: true,
|
||||
},
|
||||
})
|
||||
|
||||
await tx.stockReservation.createMany({
|
||||
data: orderItems.map(item => ({
|
||||
orderId,
|
||||
productId: item.product.id,
|
||||
quantity: item.quantity,
|
||||
inventoryId,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
async onRejectOrder(tx: Prisma.TransactionClient, orderId: number) {
|
||||
const orderItemIds = await tx.orderItem
|
||||
.findMany({
|
||||
where: { orderId },
|
||||
select: {
|
||||
productId: true,
|
||||
},
|
||||
})
|
||||
.then(items => items.map(i => i.productId))
|
||||
|
||||
await tx.stockReservation.deleteMany({
|
||||
where: {
|
||||
orderId,
|
||||
productId: { in: orderItemIds },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async onConfirmOrder(tx: Prisma.TransactionClient, orderId: number) {
|
||||
const orderItems = await tx.orderItem.findMany({
|
||||
where: { orderId },
|
||||
select: {
|
||||
product: true,
|
||||
quantity: true,
|
||||
},
|
||||
})
|
||||
|
||||
await tx.stockReservation.deleteMany({
|
||||
where: {
|
||||
orderId,
|
||||
productId: { in: orderItems.map(i => i.product.id) },
|
||||
},
|
||||
})
|
||||
|
||||
await this.salesInvoicesWorkflow.onCreateByOrder(tx, orderId)
|
||||
}
|
||||
|
||||
async onCancelOrder(tx: Prisma.TransactionClient, orderId: number) {
|
||||
const orderItemIds = await tx.orderItem
|
||||
.findMany({
|
||||
where: { orderId },
|
||||
select: {
|
||||
productId: true,
|
||||
},
|
||||
})
|
||||
.then(items => items.map(i => i.productId))
|
||||
|
||||
await tx.stockReservation.deleteMany({
|
||||
where: {
|
||||
orderId,
|
||||
productId: { in: orderItemIds },
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Controller, Get, Param, Query } from '@nestjs/common'
|
||||
import { PosService } from './pos.service'
|
||||
|
||||
@Controller('pos')
|
||||
export class PosController {
|
||||
constructor(private readonly posService: PosService) {}
|
||||
|
||||
@Get('/:posId')
|
||||
getInfo(@Param('posId') posId: string) {
|
||||
return this.posService.getInfo(Number(posId))
|
||||
}
|
||||
|
||||
@Get('/:posId/stock')
|
||||
async getStock(
|
||||
@Param('posId') posId: string,
|
||||
@Query('isAvailable') isAvailable?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@Query('q') q?: string,
|
||||
) {
|
||||
const options = {
|
||||
isAvailable: isAvailable ? isAvailable === 'true' : undefined,
|
||||
page: page ? Number(page) : undefined,
|
||||
pageSize: pageSize ? Number(pageSize) : undefined,
|
||||
q,
|
||||
}
|
||||
return this.posService.getStock(Number(posId), options)
|
||||
}
|
||||
|
||||
@Get('/:posId/product-categories')
|
||||
async getProductCategories(@Param('posId') posId: string) {
|
||||
return this.posService.getProductCategories(Number(posId))
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { PosOrdersModule } from './orders/orders.module'
|
||||
import { PosController } from './pos.controller'
|
||||
import { PosService } from './pos.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, PosOrdersModule],
|
||||
controllers: [PosController],
|
||||
providers: [PosService],
|
||||
})
|
||||
export class PosModule {}
|
||||
@@ -1,154 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class PosService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getInfo(posId: number) {
|
||||
const info = await this.prisma.posAccount.findUniqueOrThrow({
|
||||
where: { id: posId },
|
||||
include: {
|
||||
inventoryBankAccount: {
|
||||
include: {
|
||||
bankAccount: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
accountNumber: true,
|
||||
branch: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
bank: { select: { id: true, name: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inventory: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const { inventoryBankAccount, ...rest } = info
|
||||
|
||||
return ResponseMapper.single({
|
||||
...rest,
|
||||
bankAccount: inventoryBankAccount?.bankAccount,
|
||||
inventory: inventoryBankAccount?.inventory,
|
||||
})
|
||||
}
|
||||
|
||||
async getStock(
|
||||
posId: number,
|
||||
options: {
|
||||
isAvailable?: boolean
|
||||
page?: number
|
||||
pageSize?: number
|
||||
q?: string
|
||||
productIds?: number[]
|
||||
} = {},
|
||||
) {
|
||||
const { isAvailable, page = 1, pageSize = 50, q, productIds } = options
|
||||
const pos = await this.prisma.posAccount.findUniqueOrThrow({
|
||||
where: { id: posId },
|
||||
select: { inventoryId: true },
|
||||
})
|
||||
const inventoryId = pos.inventoryId
|
||||
|
||||
let sql = `
|
||||
SELECT sb.productId, sb.quantity - COALESCE(SUM(sr.quantity), 0) as availableQuantity, p.id, p.name, p.sku, p.description, p.salePrice, p.categoryId
|
||||
FROM Stock_Balance sb
|
||||
LEFT JOIN Stock_Reservations sr ON sb.productId = sr.productId AND sb.inventoryId = sr.inventoryId
|
||||
LEFT JOIN Products p ON sb.productId = p.id
|
||||
WHERE sb.inventoryId = ?
|
||||
`
|
||||
const params: any[] = [inventoryId]
|
||||
|
||||
if (productIds && productIds.length > 0) {
|
||||
sql += ` AND sb.productId IN (${productIds.map(() => '?').join(',')})`
|
||||
params.push(...productIds)
|
||||
}
|
||||
|
||||
if (q) {
|
||||
sql += ` AND (p.name LIKE ? OR p.sku LIKE ?)`
|
||||
params.push(`%${q}%`, `%${q}%`)
|
||||
}
|
||||
|
||||
sql += ` GROUP BY sb.productId`
|
||||
|
||||
if (isAvailable !== undefined) {
|
||||
sql += isAvailable
|
||||
? ` HAVING availableQuantity > 0`
|
||||
: ` HAVING availableQuantity <= 0`
|
||||
}
|
||||
|
||||
sql += ` ORDER BY sb.quantity DESC`
|
||||
|
||||
const items = await this.prisma.$queryRawUnsafe(sql, ...params)
|
||||
|
||||
const mapped = (items as any[]).map(item => ({
|
||||
id: item.productId,
|
||||
quantity: Number(item.availableQuantity),
|
||||
product: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
sku: item.sku,
|
||||
description: item.description,
|
||||
salePrice: item.salePrice,
|
||||
categoryId: item.categoryId,
|
||||
},
|
||||
}))
|
||||
|
||||
const count = mapped.length
|
||||
const paginated = mapped.slice((page - 1) * pageSize, page * pageSize)
|
||||
|
||||
return ResponseMapper.paginate(paginated, count, page, pageSize)
|
||||
}
|
||||
|
||||
async getProductCategories(posId: number) {
|
||||
const pos = await this.prisma.posAccount.findUniqueOrThrow({
|
||||
where: { id: posId },
|
||||
select: { inventoryId: true },
|
||||
})
|
||||
const inventoryId = pos.inventoryId
|
||||
|
||||
const balances = await this.prisma.stockBalance.findMany({
|
||||
where: { inventoryId },
|
||||
select: {
|
||||
product: { select: { id: true, category: { select: { id: true, name: true } } } },
|
||||
quantity: true,
|
||||
},
|
||||
})
|
||||
|
||||
const map = new Map<
|
||||
number,
|
||||
{ id: number; name: string; totalQuantity: number; productCount: number }
|
||||
>()
|
||||
|
||||
for (const b of balances) {
|
||||
const product = b.product
|
||||
const cat = product?.category
|
||||
if (!cat) continue // skip products without category
|
||||
const existing = map.get(cat.id)
|
||||
if (existing) {
|
||||
existing.totalQuantity += Number(b.quantity)
|
||||
existing.productCount += 1
|
||||
} else {
|
||||
map.set(cat.id, {
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
totalQuantity: Number(b.quantity),
|
||||
productCount: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const categories = Array.from(map.values())
|
||||
return ResponseMapper.list(categories)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class CreateProductCategoryDto {
|
||||
@IsString()
|
||||
name: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
imageUrl?: string
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class UpdateBankBranchDto {
|
||||
export class UpdateProductCategoryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { CreateProductCategoryDto } from './dto/create-product-category.dto'
|
||||
import { UpdateProductCategoryDto } from './dto/update-product-category.dto'
|
||||
import { ProductCategoriesService } from './product-categories.service'
|
||||
|
||||
@Controller('product-categories')
|
||||
export class ProductCategoriesController {
|
||||
constructor(private readonly productCategoriesService: ProductCategoriesService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateProductCategoryDto) {
|
||||
return this.productCategoriesService.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.productCategoriesService.findAll()
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.productCategoriesService.findOne(Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateProductCategoryDto) {
|
||||
return this.productCategoriesService.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.productCategoriesService.remove(Number(id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { ProductCategoriesController } from './product-categories.controller'
|
||||
import { ProductCategoriesService } from './product-categories.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [ProductCategoriesController],
|
||||
providers: [ProductCategoriesService],
|
||||
})
|
||||
export class ProductCategoriesModule {}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class ProductCategoriesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
async create(data: any) {
|
||||
const item = await this.prisma.goodCategory.create({ data })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.goodCategory.findMany()
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.goodCategory.findUnique({ where: { id } })
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const item = await this.prisma.goodCategory.update({ where: { id }, data })
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.goodCategory.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
}
|
||||
+14
-8
@@ -1,15 +1,17 @@
|
||||
import { Type } from 'class-transformer'
|
||||
import { IsInt, IsNumber, IsOptional, IsString } from 'class-validator'
|
||||
import { IsInt, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class CreateProductDto {
|
||||
@IsString()
|
||||
name: string
|
||||
|
||||
export class UpdatePurchaseReceiptDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
code?: string
|
||||
sku?: string
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
totalAmount?: number
|
||||
@IsString()
|
||||
barcode?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@@ -18,10 +20,14 @@ export class UpdatePurchaseReceiptDto {
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
supplierId?: number | null
|
||||
brandId?: number
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
inventoryId?: number | null
|
||||
categoryId?: number
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
minimumStockAlertLevel?: number
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Type } from 'class-transformer'
|
||||
import { IsInt, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class UpdateProductDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sku?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
barcode?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
brandId?: number
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
categoryId?: number
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
minimumStockAlertLevel?: number
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'
|
||||
import { ApiQuery } from '@nestjs/swagger'
|
||||
import { CreateProductDto } from './dto/create-product.dto'
|
||||
import { UpdateProductDto } from './dto/update-product.dto'
|
||||
import { ProductsService } from './products.service'
|
||||
|
||||
@Controller('products')
|
||||
export class ProductsController {
|
||||
constructor(private readonly productsService: ProductsService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateProductDto) {
|
||||
return this.productsService.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiQuery({ name: 'page', required: false, type: Number, default: 1 })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number, default: 30 })
|
||||
findAll(@Query('page') page: number = 1, @Query('pageSize') pageSize: number = 30) {
|
||||
return this.productsService.findAll(page, pageSize)
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.productsService.findOne(Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateProductDto) {
|
||||
return this.productsService.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.productsService.remove(Number(id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { ProductsController } from './products.controller'
|
||||
import { ProductsService } from './products.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [ProductsController],
|
||||
providers: [ProductsService],
|
||||
})
|
||||
export class ProductsModule {}
|
||||
@@ -0,0 +1,131 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ShortEntity } from '../../common/interfaces/response-models'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { Prisma } from '../../generated/prisma/client'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
import { CreateProductDto } from './dto/create-product.dto'
|
||||
import { UpdateProductDto } from './dto/update-product.dto'
|
||||
|
||||
@Injectable()
|
||||
export class ProductsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create(data: CreateProductDto) {
|
||||
const { brandId, categoryId, ...rest } = data
|
||||
|
||||
const dataToCreate = {
|
||||
...rest,
|
||||
brand: brandId ? { connect: { id: Number(brandId) } } : undefined,
|
||||
category: categoryId ? { connect: { id: Number(categoryId) } } : undefined,
|
||||
}
|
||||
|
||||
const item = await this.prisma.product.create({ data: dataToCreate })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async findAll(page = 1, pageSize = 20) {
|
||||
const query: Prisma.ProductFindManyArgs = {}
|
||||
|
||||
const [items, count] = await this.prisma.$transaction([
|
||||
this.prisma.product.findMany({
|
||||
include: { brand: true, category: true, stockBalances: true },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}),
|
||||
this.prisma.product.count(),
|
||||
])
|
||||
|
||||
const mapped = items.map(p => {
|
||||
const { brandId, categoryId, ...rest } = p as any
|
||||
const brand: ShortEntity | null = p.brand
|
||||
? { id: p.brand.id, name: p.brand.name }
|
||||
: null
|
||||
const category: ShortEntity | null = p.category
|
||||
? { id: p.category.id, name: p.category.name }
|
||||
: null
|
||||
const stock =
|
||||
p.stockBalances && p.stockBalances.length > 0
|
||||
? p.stockBalances.reduce((acc, sb) => acc + Number(sb.quantity), 0)
|
||||
: 0
|
||||
|
||||
return {
|
||||
...rest,
|
||||
salePrice: Number(p.salePrice),
|
||||
minimumStockAlertLevel: Number(p.minimumStockAlertLevel),
|
||||
brand,
|
||||
category,
|
||||
stock,
|
||||
}
|
||||
})
|
||||
return ResponseMapper.paginate(mapped, count, page, pageSize)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const product = await this.prisma.product.findUniqueOrThrow({
|
||||
where: { id },
|
||||
include: {
|
||||
brand: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
category: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
stockBalances: {
|
||||
select: {
|
||||
quantity: true,
|
||||
avgCost: true,
|
||||
inventory: { select: { id: true, name: true } },
|
||||
},
|
||||
},
|
||||
salesInvoiceItems: {
|
||||
select: { id: true },
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
brandId: true,
|
||||
categoryId: true,
|
||||
},
|
||||
})
|
||||
|
||||
const { salesInvoiceItems, ...rest } = product
|
||||
|
||||
return ResponseMapper.single({
|
||||
...rest,
|
||||
salePrice: Number(product.salePrice),
|
||||
minimumStockAlertLevel: Number(product.minimumStockAlertLevel),
|
||||
stock: product.stockBalances?.reduce((acc, sb) => acc + Number(sb.quantity), 0),
|
||||
avgCost:
|
||||
product.stockBalances?.reduce((acc, sb) => acc + Number(sb.avgCost), 0) /
|
||||
product.stockBalances.length,
|
||||
salesCount: product.salesInvoiceItems.length,
|
||||
})
|
||||
}
|
||||
|
||||
async update(id: number, data: UpdateProductDto) {
|
||||
const { brandId, categoryId, ...rest } = data
|
||||
|
||||
const dataToUpdate = {
|
||||
...rest,
|
||||
brand:
|
||||
brandId === null
|
||||
? { disconnect: true }
|
||||
: brandId
|
||||
? { connect: { id: Number(brandId) } }
|
||||
: undefined,
|
||||
category:
|
||||
categoryId === null
|
||||
? { disconnect: true }
|
||||
: categoryId
|
||||
? { connect: { id: Number(categoryId) } }
|
||||
: undefined,
|
||||
}
|
||||
|
||||
const item = await this.prisma.product.update({ where: { id }, data: dataToUpdate })
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.product.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Type } from 'class-transformer'
|
||||
import { ArrayMinSize, IsInt, IsNumber, IsOptional, IsString } from 'class-validator'
|
||||
import { CreatePurchaseReceiptItemDto } from '../../purchase-receipt-items/dto/create-purchase-receipt-item.dto'
|
||||
|
||||
export class CreatePurchaseReceiptDto {
|
||||
@IsString()
|
||||
code: string
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
totalAmount: number
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
paidAmount: number
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
supplierId: number
|
||||
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
inventoryId: number
|
||||
|
||||
@Type(() => Array)
|
||||
@ArrayMinSize(1)
|
||||
items: Array<Omit<CreatePurchaseReceiptItemDto, 'receiptId'>>
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { CreatePurchaseReceiptDto } from './dto/create-purchase-receipt.dto'
|
||||
import { UpdatePurchaseReceiptDto } from './dto/update-purchase-receipt.dto'
|
||||
import { PurchaseReceiptsService } from './purchase-receipts.service'
|
||||
|
||||
@Controller('purchase-receipts')
|
||||
export class PurchaseReceiptsController {
|
||||
constructor(private readonly service: PurchaseReceiptsService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreatePurchaseReceiptDto) {
|
||||
return this.service.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.service.findAll()
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.service.findOne(Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdatePurchaseReceiptDto) {
|
||||
return this.service.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.service.remove(Number(id))
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { withTransaction } from '../../../common/database/transaction.helper'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { Prisma } from '../../../generated/prisma/client'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
import { CreatePurchaseReceiptDto } from './dto/create-purchase-receipt.dto'
|
||||
import { PurchaseReceiptWorkflow } from './purchase-receipts.workflow'
|
||||
|
||||
@Injectable()
|
||||
export class PurchaseReceiptsService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private purchaseReceiptWorkflow: PurchaseReceiptWorkflow,
|
||||
) {}
|
||||
|
||||
async create(dto: CreatePurchaseReceiptDto) {
|
||||
const data: Prisma.PurchaseReceiptCreateInput = {
|
||||
code: dto.code,
|
||||
totalAmount: new Prisma.Decimal(dto.totalAmount),
|
||||
description: dto.description ?? null,
|
||||
supplier: { connect: { id: dto.supplierId } },
|
||||
inventory: { connect: { id: dto.inventoryId } },
|
||||
|
||||
items: dto.items?.length
|
||||
? {
|
||||
create: dto.items.map((item, idx) => {
|
||||
const mapped = {
|
||||
count: new Prisma.Decimal(item.count ?? 0),
|
||||
unitPrice: new Prisma.Decimal(item.unitPrice ?? 0),
|
||||
totalAmount: new Prisma.Decimal(item.totalAmount ?? 0),
|
||||
description: item.description ?? null,
|
||||
product: { connect: { id: item.productId } },
|
||||
}
|
||||
return mapped
|
||||
}),
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
|
||||
return withTransaction(this.prisma, async tx => {
|
||||
const item = await tx.purchaseReceipt.create({
|
||||
data,
|
||||
include: { items: true },
|
||||
omit: {
|
||||
supplierId: true,
|
||||
inventoryId: true,
|
||||
},
|
||||
})
|
||||
await this.purchaseReceiptWorkflow.onCreatePurchaseReceipt(
|
||||
tx,
|
||||
dto.supplierId,
|
||||
dto.totalAmount,
|
||||
item.id,
|
||||
)
|
||||
return ResponseMapper.create(item)
|
||||
})
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.purchaseReceipt.findMany({
|
||||
include: { supplier: true, inventory: true },
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.purchaseReceipt.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
supplier: {
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
inventory: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
items: true,
|
||||
},
|
||||
})
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const payload: any = { ...data }
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'supplierId')) {
|
||||
if (payload.supplierId === null) payload.supplier = { disconnect: true }
|
||||
else payload.supplier = { connect: { id: Number(payload.supplierId) } }
|
||||
delete payload.supplierId
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'inventoryId')) {
|
||||
if (payload.inventoryId === null) payload.inventory = { disconnect: true }
|
||||
else payload.inventory = { connect: { id: Number(payload.inventoryId) } }
|
||||
delete payload.inventoryId
|
||||
}
|
||||
|
||||
const item = await this.prisma.purchaseReceipt.update({
|
||||
where: { id },
|
||||
data: payload,
|
||||
})
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.purchaseReceipt.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Prisma } from '../../../generated/prisma/client'
|
||||
import { SupplierLedgerWorkflow } from '../../suppliers/ledger/supplier-ledger.workflow'
|
||||
|
||||
@Injectable()
|
||||
export class PurchaseReceiptWorkflow {
|
||||
constructor(private supplierLedgerWorkflow: SupplierLedgerWorkflow) {}
|
||||
|
||||
async onCreatePurchaseReceipt(
|
||||
tx: Prisma.TransactionClient,
|
||||
supplierId: number,
|
||||
amount: number,
|
||||
sourceId: number,
|
||||
) {
|
||||
await this.supplierLedgerWorkflow.updateLedgerBalance(
|
||||
tx,
|
||||
supplierId,
|
||||
amount,
|
||||
sourceId,
|
||||
'PURCHASE',
|
||||
)
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { CreatePurchaseReceiptItemDto } from './dto/create-purchase-receipt-item.dto'
|
||||
import { UpdatePurchaseReceiptItemDto } from './dto/update-purchase-receipt-item.dto'
|
||||
import { PurchaseReceiptItemsService } from './purchase-receipt-items.service'
|
||||
|
||||
@Controller('purchase-receipts/:receiptId/items')
|
||||
export class PurchaseReceiptItemsController {
|
||||
constructor(private readonly service: PurchaseReceiptItemsService) {}
|
||||
|
||||
@Post()
|
||||
create(
|
||||
@Param('receiptId') receiptId: string,
|
||||
@Body() dto: CreatePurchaseReceiptItemDto,
|
||||
) {
|
||||
return this.service.createForReceipt(Number(receiptId), dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Param('receiptId') receiptId: string) {
|
||||
return this.service.findAllForReceipt(Number(receiptId))
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('receiptId') receiptId: string, @Param('id') id: string) {
|
||||
return this.service.findOneForReceipt(Number(receiptId), Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(
|
||||
@Param('receiptId') receiptId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdatePurchaseReceiptItemDto,
|
||||
) {
|
||||
return this.service.updateForReceipt(Number(receiptId), Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('receiptId') receiptId: string, @Param('id') id: string) {
|
||||
return this.service.removeForReceipt(Number(receiptId), Number(id))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../../prisma/prisma.module'
|
||||
import { PurchaseReceiptItemsController } from './purchase-receipt-items.controller'
|
||||
import { PurchaseReceiptItemsService } from './purchase-receipt-items.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [PurchaseReceiptItemsController],
|
||||
providers: [PurchaseReceiptItemsService],
|
||||
})
|
||||
export class PurchaseReceiptItemsModule {}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
import { CreatePurchaseReceiptItemDto } from './dto/create-purchase-receipt-item.dto'
|
||||
|
||||
@Injectable()
|
||||
export class PurchaseReceiptItemsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create(dto: CreatePurchaseReceiptItemDto) {
|
||||
const payload: any = { ...dto }
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'productId')) {
|
||||
payload.product = { connect: { id: Number(payload.productId) } }
|
||||
delete payload.productId
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'receiptId')) {
|
||||
payload.receipt = { connect: { id: Number(payload.receiptId) } }
|
||||
delete payload.receiptId
|
||||
}
|
||||
payload.unitPrice = String(payload.unitPrice)
|
||||
payload.count = String(payload.count)
|
||||
payload.totalAmount = String(payload.totalAmount)
|
||||
const item = await this.prisma.purchaseReceiptItem.create({ data: payload })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
async findAll(receiptId: number) {
|
||||
const where = { receiptId: Number(receiptId) }
|
||||
const items = await this.prisma.purchaseReceiptItem.findMany({
|
||||
where,
|
||||
include: { product: true },
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.purchaseReceiptItem.findUnique({
|
||||
where: { id },
|
||||
include: { product: true, receipt: true },
|
||||
})
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async createForReceipt(receiptId: number, dto: CreatePurchaseReceiptItemDto) {
|
||||
const payload: any = { ...dto, receiptId }
|
||||
return this.create(payload)
|
||||
}
|
||||
|
||||
async findAllForReceipt(receiptId: number) {
|
||||
return this.findAll(receiptId)
|
||||
}
|
||||
|
||||
async findOneForReceipt(receiptId: number, id: number) {
|
||||
const item = await this.prisma.purchaseReceiptItem.findFirst({
|
||||
where: { id, receiptId },
|
||||
include: { product: true, receipt: true },
|
||||
})
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const payload: any = { ...data }
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'productId')) {
|
||||
payload.product = { connect: { id: Number(payload.productId) } }
|
||||
delete payload.productId
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'receiptId')) {
|
||||
payload.receipt = { connect: { id: Number(payload.receiptId) } }
|
||||
delete payload.receiptId
|
||||
}
|
||||
|
||||
const item = await this.prisma.purchaseReceiptItem.update({
|
||||
where: { id },
|
||||
data: payload,
|
||||
})
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async updateForReceipt(receiptId: number, id: number, data: any) {
|
||||
const existing = await this.prisma.purchaseReceiptItem.findFirst({
|
||||
where: { id, receiptId },
|
||||
})
|
||||
if (!existing) return null
|
||||
return this.update(id, data)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.purchaseReceiptItem.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async removeForReceipt(receiptId: number, id: number) {
|
||||
const existing = await this.prisma.purchaseReceiptItem.findFirst({
|
||||
where: { id, receiptId },
|
||||
})
|
||||
if (!existing) return null
|
||||
return this.remove(id)
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { IsEnum, IsInt, IsNumber, IsOptional, IsString } from 'class-validator'
|
||||
import { PaymentMethodType, PaymentType } from '../../../../generated/prisma/enums'
|
||||
|
||||
export class CreatePurchaseReceiptPaymentDto {
|
||||
@IsNumber()
|
||||
amount: number
|
||||
|
||||
@IsEnum(PaymentMethodType)
|
||||
paymentMethod: PaymentMethodType
|
||||
|
||||
@IsEnum(PaymentType)
|
||||
type: PaymentType
|
||||
|
||||
@IsInt()
|
||||
bankAccountId: number
|
||||
|
||||
@IsInt()
|
||||
receiptId: number
|
||||
|
||||
@IsString()
|
||||
payedAt: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
createdAt?: string
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger'
|
||||
import { CreatePurchaseReceiptPaymentDto } from './create-purchase-receipt-payment.dto'
|
||||
|
||||
export class UpdatePurchaseReceiptPaymentDto extends PartialType(
|
||||
CreatePurchaseReceiptPaymentDto,
|
||||
) {}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { CreatePurchaseReceiptPaymentDto } from './dto/create-purchase-receipt-payment.dto'
|
||||
import { UpdatePurchaseReceiptPaymentDto } from './dto/update-purchase-receipt-payment.dto'
|
||||
import { PurchaseReceiptPaymentsService } from './purchase-receipt-payments.service'
|
||||
|
||||
@Controller('purchase-receipt-payments')
|
||||
export class PurchaseReceiptPaymentsController {
|
||||
constructor(
|
||||
private readonly purchaseReceiptPaymentsService: PurchaseReceiptPaymentsService,
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreatePurchaseReceiptPaymentDto) {
|
||||
return this.purchaseReceiptPaymentsService.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.purchaseReceiptPaymentsService.findAll()
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.purchaseReceiptPaymentsService.findOne(Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdatePurchaseReceiptPaymentDto) {
|
||||
return this.purchaseReceiptPaymentsService.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.purchaseReceiptPaymentsService.remove(Number(id))
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
|
||||
import { PrismaModule } from '../../../prisma/prisma.module'
|
||||
import { BankAccountsModule } from '../../bank-accounts/bank-accounts.module'
|
||||
import { PurchaseReceiptPaymentsController } from './purchase-receipt-payments.controller'
|
||||
import { PurchaseReceiptPaymentsService } from './purchase-receipt-payments.service'
|
||||
import { PurchaseReceiptPaymentsWorkflow } from './purchase-receipt-payments.workflow'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, BankAccountsModule],
|
||||
controllers: [PurchaseReceiptPaymentsController],
|
||||
providers: [PurchaseReceiptPaymentsService, PurchaseReceiptPaymentsWorkflow],
|
||||
exports: [PurchaseReceiptPaymentsWorkflow],
|
||||
})
|
||||
export class PurchaseReceiptPaymentsModule {}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { withTransaction } from '../../../common/database/transaction.helper'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
import { CreatePurchaseReceiptPaymentDto } from './dto/create-purchase-receipt-payment.dto'
|
||||
import { PurchaseReceiptPaymentsWorkflow } from './purchase-receipt-payments.workflow'
|
||||
|
||||
@Injectable()
|
||||
export class PurchaseReceiptPaymentsService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private purchaseReceiptPaymentsWorkflow: PurchaseReceiptPaymentsWorkflow,
|
||||
) {}
|
||||
|
||||
async create(data: CreatePurchaseReceiptPaymentDto) {
|
||||
return withTransaction(this.prisma, async tx => {
|
||||
const item = await tx.purchaseReceiptPayments.create({ data })
|
||||
await this.purchaseReceiptPaymentsWorkflow.onAddPayment(
|
||||
tx,
|
||||
data.bankAccountId,
|
||||
data.receiptId,
|
||||
data.amount,
|
||||
data.type,
|
||||
)
|
||||
return ResponseMapper.create(item)
|
||||
})
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.purchaseReceiptPayments.findMany({
|
||||
include: {
|
||||
receipt: {
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
totalAmount: true,
|
||||
paidAmount: true,
|
||||
status: true,
|
||||
supplier: {
|
||||
select: { id: true, firstName: true, lastName: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.purchaseReceiptPayments.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
receipt: {
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
totalAmount: true,
|
||||
paidAmount: true,
|
||||
status: true,
|
||||
supplier: {
|
||||
select: { id: true, firstName: true, lastName: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const item = await this.prisma.purchaseReceiptPayments.update({ where: { id }, data })
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.purchaseReceiptPayments.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Prisma } from '../../../generated/prisma/client'
|
||||
import { PaymentType } from '../../../generated/prisma/enums'
|
||||
import { BankAccountsWorkflow } from '../../bank-accounts/bank-accounts.workflow'
|
||||
|
||||
@Injectable()
|
||||
export class PurchaseReceiptPaymentsWorkflow {
|
||||
constructor(private bankAccountsWorkflow: BankAccountsWorkflow) {}
|
||||
|
||||
async onAddPayment(
|
||||
tx: Prisma.TransactionClient,
|
||||
bankAccountId: number,
|
||||
receiptId: number,
|
||||
amount: number,
|
||||
type: PaymentType,
|
||||
) {
|
||||
const { paidAmount, totalAmount } = await tx.purchaseReceipt.findUniqueOrThrow({
|
||||
where: { id: receiptId },
|
||||
})
|
||||
|
||||
if (type === 'PAYMENT' && Number(paidAmount) + Number(amount) > Number(totalAmount)) {
|
||||
throw new Error('مجموع مبلغ پرداختی بیشتر از مبلغ فاکتور است.')
|
||||
}
|
||||
|
||||
if (type === 'REFUND' && Number(paidAmount) - Number(amount) < 0) {
|
||||
throw new Error(
|
||||
'مجموع مبلغ بازپرداختی بیشتر از مبلغ پرداخت شده در این فاکتور است.',
|
||||
)
|
||||
}
|
||||
|
||||
await this.bankAccountsWorkflow.addTransaction(tx, {
|
||||
bankAccountId: bankAccountId,
|
||||
amount: amount,
|
||||
type: type === 'PAYMENT' ? 'WITHDRAWAL' : 'DEPOSIT',
|
||||
referenceId: receiptId,
|
||||
referenceType: type === 'PAYMENT' ? 'PURCHASE_PAYMENT' : 'PURCHASE_REFUND',
|
||||
})
|
||||
|
||||
const newPaidAmount =
|
||||
type === 'PAYMENT'
|
||||
? Number(paidAmount) + Number(amount)
|
||||
: Number(paidAmount) - Number(amount)
|
||||
const newStatus =
|
||||
newPaidAmount === 0
|
||||
? 'UNPAID'
|
||||
: newPaidAmount >= Number(totalAmount)
|
||||
? 'PAID'
|
||||
: 'PARTIALLY_PAID'
|
||||
|
||||
await tx.purchaseReceipt.update({
|
||||
where: { id: receiptId },
|
||||
data: {
|
||||
paidAmount: newPaidAmount,
|
||||
status: newStatus,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { SupplierLedgerModule } from '../suppliers/ledger/supplier-ledger.module'
|
||||
import { PurchaseReceiptsController } from './index/purchase-receipts.controller'
|
||||
import { PurchaseReceiptsService } from './index/purchase-receipts.service'
|
||||
import { PurchaseReceiptWorkflow } from './index/purchase-receipts.workflow'
|
||||
import { PurchaseReceiptItemsModule } from './purchase-receipt-items/purchase-receipt-items.module'
|
||||
import { PurchaseReceiptPaymentsModule } from './purchase-receipt-payments/purchase-receipt-payments.module'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PrismaModule,
|
||||
PurchaseReceiptItemsModule,
|
||||
PurchaseReceiptPaymentsModule,
|
||||
SupplierLedgerModule,
|
||||
],
|
||||
controllers: [PurchaseReceiptsController],
|
||||
providers: [PurchaseReceiptsService, PurchaseReceiptWorkflow],
|
||||
exports: [PurchaseReceiptWorkflow],
|
||||
})
|
||||
export class PurchaseReceiptsModule {}
|
||||
+7
-8
@@ -1,25 +1,24 @@
|
||||
import { Type } from 'class-transformer'
|
||||
import { IsNotEmpty, IsNumber, Min } from 'class-validator'
|
||||
import { IsInt, IsNumber } from 'class-validator'
|
||||
|
||||
export class CreatePurchaseReceiptItemDto {
|
||||
export class CreateSalesInvoiceItemDto {
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
count: number
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
unitPrice: number
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
totalAmount: number
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNotEmpty()
|
||||
productId: number
|
||||
@IsInt()
|
||||
invoiceId: number
|
||||
|
||||
description?: string
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
productId: number
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Type } from 'class-transformer'
|
||||
import { IsInt, IsNumber, IsOptional } from 'class-validator'
|
||||
|
||||
export class UpdatePurchaseReceiptItemDto {
|
||||
export class UpdateSalesInvoiceItemDto {
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@@ -20,7 +20,7 @@ export class UpdatePurchaseReceiptItemDto {
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
receiptId?: number
|
||||
invoiceId?: number
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { CreateSalesInvoiceItemDto } from './dto/create-sales-invoice-item.dto'
|
||||
import { UpdateSalesInvoiceItemDto } from './dto/update-sales-invoice-item.dto'
|
||||
import { SalesInvoiceItemsService } from './sales-invoice-items.service'
|
||||
|
||||
@Controller('sales-invoices/:invoiceId/items')
|
||||
export class SalesInvoiceItemsController {
|
||||
constructor(private readonly service: SalesInvoiceItemsService) {}
|
||||
|
||||
@Post()
|
||||
create(@Param('invoiceId') invoiceId: string, @Body() dto: CreateSalesInvoiceItemDto) {
|
||||
return this.service.createForInvoice(Number(invoiceId), dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@Param('invoiceId') invoiceId: string) {
|
||||
return this.service.findAllForInvoice(Number(invoiceId))
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('invoiceId') invoiceId: string, @Param('id') id: string) {
|
||||
return this.service.findOneForInvoice(Number(invoiceId), Number(id))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(
|
||||
@Param('invoiceId') invoiceId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateSalesInvoiceItemDto,
|
||||
) {
|
||||
return this.service.updateForInvoice(Number(invoiceId), Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('invoiceId') invoiceId: string, @Param('id') id: string) {
|
||||
return this.service.removeForInvoice(Number(invoiceId), Number(id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { SalesInvoiceItemsController } from './sales-invoice-items.controller'
|
||||
import { SalesInvoiceItemsService } from './sales-invoice-items.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [SalesInvoiceItemsController],
|
||||
providers: [SalesInvoiceItemsService],
|
||||
})
|
||||
export class SalesInvoiceItemsModule {}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
import { CreateSalesInvoiceItemDto } from './dto/create-sales-invoice-item.dto'
|
||||
|
||||
@Injectable()
|
||||
export class SalesInvoiceItemsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create(dto: CreateSalesInvoiceItemDto) {
|
||||
const payload: any = { ...dto }
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'invoiceId')) {
|
||||
payload.invoice = { connect: { id: Number(payload.invoiceId) } }
|
||||
delete payload.invoiceId
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'productId')) {
|
||||
payload.product = { connect: { id: Number(payload.productId) } }
|
||||
delete payload.productId
|
||||
}
|
||||
|
||||
const item = await this.prisma.salesInvoiceItem.create({ data: payload })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
async findAll(invoiceId?: number) {
|
||||
const where = invoiceId ? { invoiceId: Number(invoiceId) } : undefined
|
||||
const items = await this.prisma.salesInvoiceItem.findMany({
|
||||
where,
|
||||
include: { product: true },
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.salesInvoiceItem.findUnique({
|
||||
where: { id },
|
||||
include: { product: true, invoice: true },
|
||||
})
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async createForInvoice(invoiceId: number, dto: CreateSalesInvoiceItemDto) {
|
||||
const payload: any = { ...dto, invoiceId }
|
||||
return this.create(payload)
|
||||
}
|
||||
|
||||
async findAllForInvoice(invoiceId: number) {
|
||||
return this.findAll(invoiceId)
|
||||
}
|
||||
|
||||
async findOneForInvoice(invoiceId: number, id: number) {
|
||||
const item = await this.prisma.salesInvoiceItem.findFirst({
|
||||
where: { id, invoiceId },
|
||||
include: { product: true, invoice: true },
|
||||
})
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const payload: any = { ...data }
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'productId')) {
|
||||
payload.product = { connect: { id: Number(payload.productId) } }
|
||||
delete payload.productId
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'invoiceId')) {
|
||||
payload.invoice = { connect: { id: Number(payload.invoiceId) } }
|
||||
delete payload.invoiceId
|
||||
}
|
||||
|
||||
const item = await this.prisma.salesInvoiceItem.update({
|
||||
where: { id },
|
||||
data: payload,
|
||||
})
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async updateForInvoice(invoiceId: number, id: number, data: any) {
|
||||
const existing = await this.prisma.salesInvoiceItem.findFirst({
|
||||
where: { id, invoiceId },
|
||||
})
|
||||
if (!existing) return null
|
||||
return this.update(id, data)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.salesInvoiceItem.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
|
||||
async removeForInvoice(invoiceId: number, id: number) {
|
||||
const existing = await this.prisma.salesInvoiceItem.findFirst({
|
||||
where: { id, invoiceId },
|
||||
})
|
||||
if (!existing) return null
|
||||
return this.remove(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get } from '@nestjs/common'
|
||||
import { StockBalanceService } from './stock-balance.service'
|
||||
|
||||
@Controller('stock-balance')
|
||||
export class StockBalanceController {
|
||||
constructor(private readonly service: StockBalanceService) {}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.service.findAll()
|
||||
}
|
||||
|
||||
// @Get(':itemId')
|
||||
// findOne(@Param('itemId') itemId: string) {
|
||||
// return this.service.findOne(Number(itemId))
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { StockBalanceController } from './stock-balance.controller'
|
||||
import { StockBalanceService } from './stock-balance.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [StockBalanceController],
|
||||
providers: [StockBalanceService],
|
||||
})
|
||||
export class StockBalanceModule {}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class StockBalanceService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.stockBalance.findMany()
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
// async findOne(productId: number) {
|
||||
// const item = await this.prisma.stockBalance.findUnique({ where: { productId: productId } })
|
||||
// if (!item) return null
|
||||
// return ResponseMapper.single(item)
|
||||
// }
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { CreateSupplierDto } from './dto/create-supplier.dto'
|
||||
import { UpdateSupplierDto } from './dto/update-supplier.dto'
|
||||
import { SuppliersService } from './suppliers.service'
|
||||
|
||||
@Controller('suppliers')
|
||||
export class SuppliersController {
|
||||
constructor(private readonly suppliersService: SuppliersService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateSupplierDto) {
|
||||
return this.suppliersService.create(dto)
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.suppliersService.findAll()
|
||||
}
|
||||
|
||||
@Get(':supplierId')
|
||||
findOne(@Param('supplierId') supplierId: string) {
|
||||
return this.suppliersService.findOne(Number(supplierId))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateSupplierDto) {
|
||||
return this.suppliersService.update(Number(id), dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.suppliersService.remove(Number(id))
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class SuppliersService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
async create(data: any) {
|
||||
const item = await this.prisma.supplier.create({ data })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.supplier.findMany()
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.supplier.findUnique({ where: { id } })
|
||||
if (!item) return null
|
||||
const receiptsInfo = await this.prisma.purchaseReceipt.aggregate({
|
||||
where: {
|
||||
supplierId: id,
|
||||
},
|
||||
_count: true,
|
||||
_sum: {
|
||||
totalAmount: true,
|
||||
},
|
||||
})
|
||||
|
||||
return ResponseMapper.single({
|
||||
...item,
|
||||
receiptsOverview: {
|
||||
totalPayment: receiptsInfo._sum.totalAmount,
|
||||
count: receiptsInfo._count,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
const item = await this.prisma.supplier.update({ where: { id }, data })
|
||||
return ResponseMapper.update(item)
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const item = await this.prisma.supplier.delete({ where: { id } })
|
||||
return ResponseMapper.single(item)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import {
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator'
|
||||
import { PaymentMethodType, PaymentType } from '../../../../generated/prisma/enums'
|
||||
|
||||
export class CreateReceiptPaymentDto {
|
||||
@IsNumber()
|
||||
amount: number
|
||||
|
||||
@IsEnum(PaymentMethodType)
|
||||
paymentMethod: PaymentMethodType
|
||||
|
||||
@IsEnum(PaymentType)
|
||||
type: PaymentType
|
||||
|
||||
@IsInt()
|
||||
bankAccountId: number
|
||||
|
||||
@IsDateString()
|
||||
payedAt: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
createdAt?: string
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
|
||||
import { CreateReceiptPaymentDto } from './dto/create-receipt-payment.dto'
|
||||
import { SupplierInvoicesService } from './invoices.service'
|
||||
|
||||
@Controller('suppliers/:supplierId/invoices')
|
||||
export class SupplierInvoicesController {
|
||||
constructor(private readonly supplierInvoicesService: SupplierInvoicesService) {}
|
||||
|
||||
@Get()
|
||||
getInvoices(@Param('supplierId') supplierId: string) {
|
||||
return this.supplierInvoicesService.findAll(Number(supplierId))
|
||||
}
|
||||
|
||||
@Get(':invoiceId')
|
||||
getInvoice(
|
||||
@Param('supplierId') supplierId: string,
|
||||
@Param('invoiceId') invoiceId: string,
|
||||
) {
|
||||
return this.supplierInvoicesService.findOne(Number(supplierId), Number(invoiceId))
|
||||
}
|
||||
|
||||
@Post(':invoiceId/pay')
|
||||
createPayment(
|
||||
@Param('supplierId') supplierId: string,
|
||||
@Param('invoiceId') invoiceId: string,
|
||||
@Body() data: CreateReceiptPaymentDto,
|
||||
) {
|
||||
return this.supplierInvoicesService.createPayment(
|
||||
Number(supplierId),
|
||||
Number(invoiceId),
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
@Get(':invoiceId/payments')
|
||||
getInvoicePayments(
|
||||
@Param('supplierId') supplierId: string,
|
||||
@Param('invoiceId') invoiceId: string,
|
||||
) {
|
||||
return this.supplierInvoicesService.findPayments(Number(invoiceId))
|
||||
}
|
||||
|
||||
@Get('payments')
|
||||
getPayments(@Param('supplierId') supplierId: string) {
|
||||
return this.supplierInvoicesService.findPayments(Number(supplierId))
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../../prisma/prisma.module'
|
||||
import { PurchaseReceiptPaymentsModule } from '../../purchase-receipts/purchase-receipt-payments/purchase-receipt-payments.module'
|
||||
import { SupplierLedgerModule } from '../ledger/supplier-ledger.module'
|
||||
import { SupplierInvoicesController } from './invoices.controller'
|
||||
import { SupplierInvoicesService } from './invoices.service'
|
||||
import { SupplierInvoicesWorkflow } from './invoices.workflow'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, PurchaseReceiptPaymentsModule, SupplierLedgerModule],
|
||||
controllers: [SupplierInvoicesController],
|
||||
providers: [SupplierInvoicesService, SupplierInvoicesWorkflow],
|
||||
})
|
||||
export class SupplierInvoicesModule {}
|
||||
@@ -1,196 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { withTransaction } from '../../../common/database/transaction.helper'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
import { CreateReceiptPaymentDto } from './dto/create-receipt-payment.dto'
|
||||
import { SupplierInvoicesWorkflow } from './invoices.workflow'
|
||||
|
||||
@Injectable()
|
||||
export class SupplierInvoicesService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private supplierInvoicesWorkflow: SupplierInvoicesWorkflow,
|
||||
) {}
|
||||
|
||||
async findAll(supplierId: number) {
|
||||
const items = await this.prisma.purchaseReceipt.findMany({
|
||||
where: {
|
||||
supplierId,
|
||||
},
|
||||
include: {
|
||||
inventory: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
items: {
|
||||
select: {
|
||||
id: true,
|
||||
count: true,
|
||||
unitPrice: true,
|
||||
totalAmount: true,
|
||||
product: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sku: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
payments: {
|
||||
select: {
|
||||
id: true,
|
||||
amount: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
supplierId: true,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findOne(supplierId: number, invoiceId: number) {
|
||||
const invoice = await this.prisma.purchaseReceipt.findUnique({
|
||||
where: {
|
||||
id: invoiceId,
|
||||
supplierId,
|
||||
},
|
||||
include: {
|
||||
inventory: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
items: true,
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
supplierId: true,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.single(invoice)
|
||||
}
|
||||
|
||||
async findPayments(invoiceId: number) {
|
||||
const items = await this.prisma.purchaseReceiptPayments.findMany({
|
||||
where: {
|
||||
receipt: {
|
||||
id: invoiceId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
receipt: {
|
||||
include: {
|
||||
inventory: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
bankAccount: {
|
||||
select: {
|
||||
branch: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
bank: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
shortName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountNumber: true,
|
||||
cardNumber: true,
|
||||
name: true,
|
||||
id: true,
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
receiptId: true,
|
||||
},
|
||||
orderBy: {
|
||||
payedAt: 'desc',
|
||||
},
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async findAllPayments(supplierId: number) {
|
||||
const items = await this.prisma.purchaseReceiptPayments.findMany({
|
||||
where: {
|
||||
receipt: {
|
||||
supplierId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
receipt: {
|
||||
select: {
|
||||
id: true,
|
||||
totalAmount: true,
|
||||
status: true,
|
||||
paidAmount: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
receiptId: true,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async createPayment(
|
||||
supplierId: number,
|
||||
invoiceId: number,
|
||||
data: CreateReceiptPaymentDto,
|
||||
) {
|
||||
const { bankAccountId, ...rest } = data
|
||||
|
||||
return withTransaction(this.prisma, async tx => {
|
||||
await this.supplierInvoicesWorkflow.onPaymentCreated(
|
||||
tx,
|
||||
supplierId,
|
||||
data.amount,
|
||||
invoiceId,
|
||||
bankAccountId,
|
||||
)
|
||||
|
||||
const payment = await tx.purchaseReceiptPayments.create({
|
||||
data: {
|
||||
...rest,
|
||||
payedAt: new Date(data.payedAt),
|
||||
receipt: {
|
||||
connect: {
|
||||
id: invoiceId,
|
||||
},
|
||||
},
|
||||
bankAccount: {
|
||||
connect: {
|
||||
id: bankAccountId,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
receipt: true,
|
||||
},
|
||||
omit: {
|
||||
receiptId: true,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.create(payment)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Prisma } from '../../../generated/prisma/client'
|
||||
import { PurchaseReceiptPaymentsWorkflow } from '../../purchase-receipts/purchase-receipt-payments/purchase-receipt-payments.workflow'
|
||||
import { SupplierLedgerWorkflow } from '../ledger/supplier-ledger.workflow'
|
||||
|
||||
@Injectable()
|
||||
export class SupplierInvoicesWorkflow {
|
||||
constructor(
|
||||
private supplierLedgerWorkflow: SupplierLedgerWorkflow,
|
||||
private purchaseReceiptPaymentWorkflow: PurchaseReceiptPaymentsWorkflow,
|
||||
) {}
|
||||
|
||||
async onPaymentCreated(
|
||||
tx: Prisma.TransactionClient,
|
||||
supplierId: number,
|
||||
amount: number,
|
||||
invoiceId: number,
|
||||
bankAccountId: number,
|
||||
) {
|
||||
await this.supplierLedgerWorkflow.updateLedgerBalance(
|
||||
tx,
|
||||
supplierId,
|
||||
amount,
|
||||
invoiceId,
|
||||
'PAYMENT',
|
||||
)
|
||||
|
||||
await this.purchaseReceiptPaymentWorkflow.onAddPayment(
|
||||
tx,
|
||||
bankAccountId,
|
||||
invoiceId,
|
||||
amount,
|
||||
'PAYMENT',
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Controller, Get, Param } from '@nestjs/common'
|
||||
import { SupplierLedgerService } from './supplier-ledger.service'
|
||||
|
||||
@Controller('suppliers/:supplierId/ledger')
|
||||
export class SupplierLedgerController {
|
||||
constructor(private readonly supplierLedgerService: SupplierLedgerService) {}
|
||||
|
||||
@Get('')
|
||||
getLedger(@Param('supplierId') supplierId: string) {
|
||||
return this.supplierLedgerService.findAll(Number(supplierId))
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../../prisma/prisma.module'
|
||||
import { SupplierLedgerController } from './supplier-ledger.controller'
|
||||
import { SupplierLedgerService } from './supplier-ledger.service'
|
||||
import { SupplierLedgerWorkflow } from './supplier-ledger.workflow'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [SupplierLedgerController],
|
||||
providers: [SupplierLedgerService, SupplierLedgerWorkflow],
|
||||
exports: [SupplierLedgerWorkflow],
|
||||
})
|
||||
export class SupplierLedgerModule {}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../../../common/response/response-mapper'
|
||||
import { PrismaService } from '../../../prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class SupplierLedgerService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findAll(supplierId: number) {
|
||||
const items = await this.prisma.supplierLedger.findMany({
|
||||
where: {
|
||||
supplierId,
|
||||
},
|
||||
omit: {
|
||||
supplierId: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Prisma } from '../../../generated/prisma/client'
|
||||
import { LedgerSourceType } from '../../../generated/prisma/enums'
|
||||
|
||||
@Injectable()
|
||||
export class SupplierLedgerWorkflow {
|
||||
async updateLedgerBalance(
|
||||
tx: Prisma.TransactionClient,
|
||||
supplierId: number,
|
||||
amount: number,
|
||||
sourceId: number,
|
||||
sourceType: LedgerSourceType,
|
||||
) {
|
||||
const lastSupplierLedgerBalance = await tx.supplierLedger
|
||||
.findFirstOrThrow({
|
||||
where: { supplierId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
.then(sl => (sl ? sl.balance : 0))
|
||||
|
||||
let newBalance = Number(lastSupplierLedgerBalance)
|
||||
let credit = 0
|
||||
let debit = 0
|
||||
let description = ''
|
||||
|
||||
switch (sourceType) {
|
||||
case 'PURCHASE':
|
||||
newBalance += amount
|
||||
credit = amount
|
||||
description = `خرید به مبلغ ${amount} بابت خرید شماره ${sourceId}`
|
||||
break
|
||||
case 'REFUND':
|
||||
case 'PAYMENT':
|
||||
newBalance -= amount
|
||||
debit = amount
|
||||
description = `پرداخت به مبلغ ${amount} بابت پرداخت شماره ${sourceId}`
|
||||
break
|
||||
case 'ADJUSTMENT':
|
||||
newBalance = amount
|
||||
description = `اصلاح حساب به مبلغ ${amount} بابت پرداخت شماره ${sourceId}`
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return await tx.supplierLedger.create({
|
||||
data: {
|
||||
supplierId,
|
||||
debit,
|
||||
credit,
|
||||
balance: newBalance,
|
||||
sourceId,
|
||||
sourceType,
|
||||
description,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { SuppliersController } from './index/suppliers.controller'
|
||||
import { SuppliersService } from './index/suppliers.service'
|
||||
import { SupplierInvoicesModule } from './invoices/invoices.module'
|
||||
import { SupplierLedgerModule } from './ledger/supplier-ledger.module'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, SupplierInvoicesModule, SupplierLedgerModule],
|
||||
controllers: [SuppliersController],
|
||||
providers: [SuppliersService],
|
||||
})
|
||||
export class SuppliersModule {}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { File } from 'multer'
|
||||
import { UploaderService } from './uploader.service'
|
||||
|
||||
@Injectable()
|
||||
export class ImageUploaderService {
|
||||
constructor(private readonly uploaderService: UploaderService) {}
|
||||
|
||||
async uploadImage(
|
||||
file: File,
|
||||
accountId: string,
|
||||
type: string,
|
||||
): Promise<string> {
|
||||
return this.uploaderService.uploadFile(
|
||||
file.buffer,
|
||||
file.originalname,
|
||||
accountId,
|
||||
type,
|
||||
)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user