invoice update
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { CreateInvoiceItemDto } from './create-invoice.dto';
|
||||||
|
|
||||||
|
export class UpdateInvoiceItemDto extends PartialType(CreateInvoiceItemDto) {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@ApiPropertyOptional({ description: 'Invoice item ID - when provided, updates existing item; when omitted, creates new item' })
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
@@ -1,4 +1,23 @@
|
|||||||
import { PartialType } from '@nestjs/swagger';
|
import { IntersectionType, OmitType, PartialType } from '@nestjs/swagger';
|
||||||
|
import { IsArray, IsOptional } from 'class-validator';
|
||||||
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
import { CreateInvoiceDto } from './create-invoice.dto';
|
import { CreateInvoiceDto } from './create-invoice.dto';
|
||||||
|
import { UpdateInvoiceItemDto } from './update-invoice-item.dto';
|
||||||
|
|
||||||
export class UpdateInvoiceDto extends PartialType(CreateInvoiceDto) {}
|
class UpdateInvoiceBaseDto extends PartialType(
|
||||||
|
OmitType(CreateInvoiceDto, ['items']),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
class UpdateInvoiceItemsDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => UpdateInvoiceItemDto)
|
||||||
|
@ApiPropertyOptional({ type: [UpdateInvoiceItemDto] })
|
||||||
|
items?: UpdateInvoiceItemDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateInvoiceDto extends IntersectionType(
|
||||||
|
UpdateInvoiceBaseDto,
|
||||||
|
UpdateInvoiceItemsDto,
|
||||||
|
) {}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Request } from '../request/entities/request.entity';
|
|||||||
import { ProductService } from '../product/providers/product.service';
|
import { ProductService } from '../product/providers/product.service';
|
||||||
import { InvoiceRepository } from './repositories/invoice.repository';
|
import { InvoiceRepository } from './repositories/invoice.repository';
|
||||||
|
|
||||||
const TAX_RATE = 0.09;
|
const TAX_RATE = 0.1;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InvoiceService {
|
export class InvoiceService {
|
||||||
@@ -127,6 +127,72 @@ export class InvoiceService {
|
|||||||
if (updateInvoiceDto.approvalDeadline !== undefined) {
|
if (updateInvoiceDto.approvalDeadline !== undefined) {
|
||||||
invoice.approvalDeadline = new Date(updateInvoiceDto.approvalDeadline);
|
invoice.approvalDeadline = new Date(updateInvoiceDto.approvalDeadline);
|
||||||
}
|
}
|
||||||
|
if (updateInvoiceDto.items !== undefined && updateInvoiceDto.items.length > 0) {
|
||||||
|
for (const item of updateInvoiceDto.items) {
|
||||||
|
if (item.id) {
|
||||||
|
const existingItem = await this.em.findOne(InvoiceItem, {
|
||||||
|
id: item.id,
|
||||||
|
invoice: { id: invoice.id },
|
||||||
|
});
|
||||||
|
if (!existingItem) {
|
||||||
|
throw new NotFoundException(`Invoice item ${item.id} not found`);
|
||||||
|
}
|
||||||
|
if (item.productId !== undefined) {
|
||||||
|
existingItem.product = await this.productService.findOneOrFail(
|
||||||
|
item.productId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (item.quantity !== undefined) existingItem.quantity = item.quantity;
|
||||||
|
if (item.unitPrice !== undefined)
|
||||||
|
existingItem.unitPrice = item.unitPrice;
|
||||||
|
if (item.discount !== undefined) existingItem.discount = item.discount;
|
||||||
|
if (item.description !== undefined)
|
||||||
|
existingItem.description = item.description;
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
item.productId === undefined ||
|
||||||
|
item.quantity === undefined ||
|
||||||
|
item.unitPrice === undefined
|
||||||
|
) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'New invoice items must have productId, quantity and unitPrice',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const product = await this.productService.findOneOrFail(item.productId);
|
||||||
|
const itemDiscount = item.discount ?? 0;
|
||||||
|
const invoiceItem = this.em.create(InvoiceItem, {
|
||||||
|
invoice,
|
||||||
|
product,
|
||||||
|
quantity: item.quantity,
|
||||||
|
unitPrice: item.unitPrice,
|
||||||
|
discount: itemDiscount || undefined,
|
||||||
|
description: item.description,
|
||||||
|
});
|
||||||
|
invoice.items.add(invoiceItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allItems = invoice.items.getItems();
|
||||||
|
let subTotal = 0;
|
||||||
|
let discount = 0;
|
||||||
|
for (const item of allItems) {
|
||||||
|
const itemTotal = Math.max(
|
||||||
|
0,
|
||||||
|
item.unitPrice * item.quantity - (item.discount ?? 0),
|
||||||
|
);
|
||||||
|
subTotal += itemTotal;
|
||||||
|
discount += item.discount ?? 0;
|
||||||
|
}
|
||||||
|
invoice.discount = discount;
|
||||||
|
const taxAmount = invoice.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 this.em.flush();
|
await this.em.flush();
|
||||||
return invoice;
|
return invoice;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user