refactor: restructure purchase receipts module and related workflows

- Removed old DTOs for creating and updating purchase receipts.
- Updated purchase receipts controller and service to use new DTOs and workflows.
- Introduced transaction helper for managing database transactions.
- Added new workflows for handling purchase receipt payments and supplier ledgers.
- Implemented new logic for managing purchase receipt items and payments.
- Enhanced error handling for payment processing in workflows.
- Updated supplier ledger management to reflect changes in purchase receipts.
This commit is contained in:
2026-01-05 10:07:23 +03:30
parent a2db2daa70
commit fda190f902
38 changed files with 617 additions and 486 deletions
@@ -2,10 +2,12 @@ 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],
providers: [BankAccountsService, BankAccountsWorkflow],
exports: [BankAccountsWorkflow],
})
export class BankAccountsModule {}
@@ -1,4 +1,5 @@
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'
@@ -6,8 +7,16 @@ import { PrismaService } from '../../prisma/prisma.service'
export class BankAccountsService {
constructor(private prisma: PrismaService) {}
async create(data: any) {
const item = await this.prisma.bankAccount.create({ data })
return ResponseMapper.create(item)
return withTransaction(this.prisma, async tx => {
const item = await tx.bankAccount.create({ data })
await tx.bankAccountBalance.create({
data: {
bankAccountId: item.id,
balance: 0,
},
})
return ResponseMapper.create(item)
})
}
async findAll() {
@@ -0,0 +1,65 @@
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.bankAccountBalance.findUnique({
where: {
bankAccountId: payload.bankAccountId,
},
})
const balance = item ? Number(item.balance) : 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,
},
})
console.log('first')
const bankAccountBalanceItem = await tx.bankAccountBalance.findUnique({
where: {
bankAccountId: payload.bankAccountId,
},
})
if (!bankAccountBalanceItem) {
return await tx.bankAccountBalance.create({
data: {
bankAccount: { connect: { id: payload.bankAccountId } },
balance: newBalance,
},
})
}
return await tx.bankAccountBalance.update({
where: {
bankAccountId: payload.bankAccountId,
},
data: {
balance: newBalance,
},
})
}
}
@@ -0,0 +1,22 @@
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,12 +0,0 @@
import { Module } from '@nestjs/common'
import { PrismaModule } from '../../prisma/prisma.module'
import { PurchaseReceiptPaymentsController } from './purchase-receipt-payments.controller'
import { PurchaseReceiptPaymentsService } from './purchase-receipt-payments.service'
@Module({
imports: [PrismaModule],
controllers: [PurchaseReceiptPaymentsController],
providers: [PurchaseReceiptPaymentsService],
})
export class PurchaseReceiptPaymentsModule {}
@@ -0,0 +1,32 @@
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'>>
}
@@ -0,0 +1,27 @@
import { Type } from 'class-transformer'
import { IsInt, IsNumber, IsOptional, IsString } from 'class-validator'
export class UpdatePurchaseReceiptDto {
@IsOptional()
@IsString()
code?: string
@IsOptional()
@Type(() => Number)
@IsNumber()
totalAmount?: number
@IsOptional()
@IsString()
description?: string
@IsOptional()
@Type(() => Number)
@IsInt()
supplierId?: number | null
@IsOptional()
@Type(() => Number)
@IsInt()
inventoryId?: number | null
}
@@ -0,0 +1,34 @@
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))
}
}
@@ -0,0 +1,114 @@
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)
}
}
@@ -0,0 +1,23 @@
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',
)
}
}
@@ -0,0 +1,25 @@
import { Type } from 'class-transformer'
import { IsNotEmpty, IsNumber, Min } from 'class-validator'
export class CreatePurchaseReceiptItemDto {
@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
description?: string
}
@@ -0,0 +1,29 @@
import { Type } from 'class-transformer'
import { IsInt, IsNumber, IsOptional } from 'class-validator'
export class UpdatePurchaseReceiptItemDto {
@IsOptional()
@Type(() => Number)
@IsNumber()
count?: number
@IsOptional()
@Type(() => Number)
@IsNumber()
unitPrice?: number
@IsOptional()
@Type(() => Number)
@IsNumber()
total?: number
@IsOptional()
@Type(() => Number)
@IsInt()
receiptId?: number
@IsOptional()
@Type(() => Number)
@IsInt()
productId?: number
}
@@ -0,0 +1,41 @@
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))
}
}
@@ -0,0 +1,11 @@
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 {}
@@ -0,0 +1,100 @@
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.total = String(payload.total)
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)
}
}
@@ -1,5 +1,5 @@
import { IsEnum, IsInt, IsNumber, IsOptional, IsString } from 'class-validator'
import { PaymentMethodType, PaymentType } from '../../../generated/prisma/enums'
import { PaymentMethodType, PaymentType } from '../../../../generated/prisma/enums'
export class CreatePurchaseReceiptPaymentDto {
@IsNumber()
@@ -0,0 +1,15 @@
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 {}
@@ -1,14 +1,29 @@
import { Injectable } from '@nestjs/common'
import { ResponseMapper } from '../../common/response/response-mapper'
import { PrismaService } from '../../prisma/prisma.service'
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) {}
constructor(
private prisma: PrismaService,
private purchaseReceiptPaymentsWorkflow: PurchaseReceiptPaymentsWorkflow,
) {}
async create(data: any) {
const item = await this.prisma.purchaseReceiptPayments.create({ data })
return ResponseMapper.create(item)
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() {
@@ -0,0 +1,58 @@
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,
},
})
}
}
@@ -0,0 +1,21 @@
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 {}
@@ -22,11 +22,6 @@ export class SuppliersController {
return this.suppliersService.findOne(Number(supplierId))
}
@Get(':supplierId/ledger')
getLedger(@Param('supplierId') supplierId: string) {
return this.suppliersService.getLedger(Number(supplierId))
}
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateSupplierDto) {
return this.suppliersService.update(Number(id), dto)
@@ -37,21 +37,6 @@ export class SuppliersService {
})
}
async getLedger(supplierId: number) {
const items = await this.prisma.supplierLedger.findMany({
where: {
supplierId,
},
omit: {
supplierId: true,
},
orderBy: {
createdAt: 'desc',
},
})
return ResponseMapper.list(items)
}
async update(id: number, data: any) {
const item = await this.prisma.supplier.update({ where: { id }, data })
return ResponseMapper.update(item)
@@ -21,10 +21,15 @@ export class SupplierInvoicesController {
@Post(':invoiceId/pay')
createPayment(
@Param('supplierId') supplierId: string,
@Param('invoiceId') invoiceId: string,
@Body() data: CreateReceiptPaymentDto,
) {
return this.supplierInvoicesService.createPayment(Number(invoiceId), data)
return this.supplierInvoicesService.createPayment(
Number(supplierId),
Number(invoiceId),
data,
)
}
@Get(':invoiceId/payments')
@@ -1,11 +1,14 @@
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],
imports: [PrismaModule, PurchaseReceiptPaymentsModule, SupplierLedgerModule],
controllers: [SupplierInvoicesController],
providers: [SupplierInvoicesService],
providers: [SupplierInvoicesService, SupplierInvoicesWorkflow],
})
export class SupplierInvoicesModule {}
@@ -1,11 +1,16 @@
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) {}
constructor(
private prisma: PrismaService,
private supplierInvoicesWorkflow: SupplierInvoicesWorkflow,
) {}
async findAll(supplierId: number) {
const items = await this.prisma.purchaseReceipt.findMany({
@@ -147,32 +152,45 @@ export class SupplierInvoicesService {
return ResponseMapper.list(items)
}
async createPayment(invoiceId: number, data: CreateReceiptPaymentDto) {
async createPayment(
supplierId: number,
invoiceId: number,
data: CreateReceiptPaymentDto,
) {
const { bankAccountId, ...rest } = data
console.log(data)
const payment = await this.prisma.purchaseReceiptPayments.create({
data: {
...rest,
payedAt: new Date(data.payedAt),
receipt: {
connect: {
id: invoiceId,
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,
},
},
},
bankAccount: {
connect: {
id: bankAccountId,
},
include: {
receipt: true,
},
},
include: {
receipt: true,
},
omit: {
receiptId: true,
},
omit: {
receiptId: true,
},
})
return ResponseMapper.create(payment)
})
return ResponseMapper.create(payment)
}
}
@@ -0,0 +1,36 @@
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',
)
}
}
@@ -0,0 +1,12 @@
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))
}
}
@@ -0,0 +1,13 @@
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 {}
@@ -0,0 +1,23 @@
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)
}
}
@@ -0,0 +1,57 @@
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,
},
})
}
}
+2 -1
View File
@@ -3,9 +3,10 @@ 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],
imports: [PrismaModule, SupplierInvoicesModule, SupplierLedgerModule],
controllers: [SuppliersController],
providers: [SuppliersService],
})