This commit is contained in:
2026-02-18 23:58:03 +03:30
parent 6b2aeddedd
commit 5cc9211e49
9 changed files with 443 additions and 33 deletions
+137 -11
View File
@@ -1,26 +1,152 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { CreateInvoiceDto } from './dto/create-invoice.dto';
import { UpdateInvoiceDto } from './dto/update-invoice.dto';
import { FindInvoicesDto } from './dto/find-invoices.dto';
import { Invoice } from './entities/invoice.entity';
import { InvoiceItem } from './entities/invoice-item.entity';
import { Request } from '../request/entities/request.entity';
import { ProductService } from '../product/providers/product.service';
import { InvoiceRepository } from './repositories/invoice.repository';
const TAX_RATE = 0.09;
@Injectable()
export class InvoiceService {
create(createInvoiceDto: CreateInvoiceDto) {
return 'This action adds a new invoice';
constructor(
private readonly em: EntityManager,
private readonly productService: ProductService,
private readonly invoiceRepository: InvoiceRepository,
) { }
async create(dto: CreateInvoiceDto) {
const request = await this.em.findOne(
Request,
{ id: dto.requestId },
{ populate: ['user'] },
);
if (!request) {
throw new NotFoundException('Request not found');
}
const enableTax = dto.enableTax ?? false;
const approvalDeadline = dto.approvalDeadline
? new Date(dto.approvalDeadline)
: undefined;
const discount = dto.items.reduce((acc, item) => acc + (item.discount ?? 0), 0);
return this.em.transactional(async (em) => {
const invoice = em.create(Invoice, {
user: request.user,
request,
enableTax,
approvalDeadline,
discount,
paidAmount: 0,
balance: 0,
});
em.persist(invoice);
let subTotal = 0;
for (const item of dto.items) {
const product = await this.productService.findOneOrFail(item.productId);
const unitPrice = item.unitPrice;
const itemDiscount = item.discount ?? 0;
const itemTotal = Math.max(0, unitPrice * item.quantity - itemDiscount);
subTotal += itemTotal;
const invoiceItem = em.create(InvoiceItem, {
invoice,
product,
quantity: item.quantity,
unitPrice: unitPrice,
discount: itemDiscount || undefined,
description: item.description,
});
invoice.items.add(invoiceItem);
}
const taxAmount = enableTax
? Math.round((subTotal - discount) * TAX_RATE)
: 0;
const total = Math.max(0, subTotal - discount + taxAmount);
invoice.subTotal = subTotal;
invoice.taxAmount = taxAmount;
invoice.total = total;
invoice.balance = total - invoice.paidAmount;
await em.flush();
return invoice;
});
}
findAll() {
return `This action returns all invoice`;
findAll(userId: string, dto: FindInvoicesDto) {
return this.invoiceRepository.findAllPaginated({ userId, ...dto });
}
findOne(id: number) {
return `This action returns a #${id} invoice`;
findAllAsAdmin(dto: FindInvoicesDto) {
return this.invoiceRepository.findAllPaginated(dto);
}
update(id: number, updateInvoiceDto: UpdateInvoiceDto) {
return `This action updates a #${id} invoice`;
async findOne(id: string, userId: string) {
const invoice = await this.em.findOne(
Invoice,
{ id, user: { id: userId } },
{ populate: ['user', 'request', 'items', 'items.product'] },
);
if (!invoice) {
throw new NotFoundException('Invoice not found');
}
return invoice;
}
remove(id: number) {
return `This action removes a #${id} invoice`;
async confirmInvoiceItem(id: string, userId: string) {
const item = await this.em.findOne(
InvoiceItem,
{ id, invoice: { user: { id: userId } } },
{ populate: ['invoice', 'product'] },
);
if (!item) {
throw new NotFoundException('Invoice item not found');
}
if(item.confirmedAt){
throw new BadRequestException('Invoice item already confirmed');
}
item.confirmedAt = new Date();
await this.em.persistAndFlush(item);
return item;
}
async update(id: string, updateInvoiceDto: UpdateInvoiceDto) {
const invoice = await this.findOneOrFail(id);
if (updateInvoiceDto.enableTax !== undefined) invoice.enableTax = updateInvoiceDto.enableTax;
if (updateInvoiceDto.approvalDeadline !== undefined) {
invoice.approvalDeadline = new Date(updateInvoiceDto.approvalDeadline);
}
await this.em.flush();
return invoice;
}
async remove(id: string) {
const invoice = await this.findOneOrFail(id);
this.em.remove(invoice);
await this.em.flush();
return invoice;
}
async findOneOrFail(id: string) {
const invoice = await this.em.findOne(
Invoice,
{ id },
{ populate: ['user', 'request', 'items', 'items.product'] },
);
if (!invoice) {
throw new NotFoundException('Invoice not found');
}
return invoice;
}
}