consolidate migrations
This commit is contained in:
@@ -33,6 +33,12 @@ export class CreateInvoiceItemDto {
|
||||
@ApiPropertyOptional({ example: 0 })
|
||||
discount?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@ApiPropertyOptional({ example: 10, description: 'Discount percentage (0–100)' })
|
||||
discountPercent?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiPropertyOptional()
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Order } from 'src/modules/order/entities/order.entity';
|
||||
@Entity({ tableName: 'invoice_items' })
|
||||
@Index({ properties: ['invoice'] })
|
||||
export class InvoiceItem extends BaseEntity {
|
||||
[OptionalProps]?: 'createdAt' | 'deletedAt' | 'discount' | 'description' | 'confirmedAt';
|
||||
[OptionalProps]?: 'createdAt' | 'deletedAt' | 'discount' | 'discountPercent' | 'description' | 'confirmedAt';
|
||||
|
||||
@ManyToOne(() => Invoice)
|
||||
invoice!: Invoice;
|
||||
@@ -38,6 +38,9 @@ export class InvoiceItem extends BaseEntity {
|
||||
})
|
||||
discount?: number;
|
||||
|
||||
@Property({ type: 'int', nullable: true, fieldName: 'discount_percent' })
|
||||
discountPercent?: number;
|
||||
|
||||
@Property({
|
||||
type: 'int',
|
||||
columnType: 'int GENERATED ALWAYS AS ((COALESCE(unit_price, 0) * quantity) - COALESCE(discount, 0)) STORED',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { CreateInvoiceDto } from './dto/create-invoice.dto';
|
||||
import { CreateInvoiceDto, CreateInvoiceItemDto } 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';
|
||||
@@ -15,6 +15,11 @@ import { RequestStatusEnum } from '../request/enum/request';
|
||||
|
||||
const TAX_RATE = 0.1;
|
||||
|
||||
type ResolvedItemDiscount = {
|
||||
discount?: number;
|
||||
discountPercent?: number;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class InvoiceService {
|
||||
constructor(
|
||||
@@ -24,6 +29,34 @@ export class InvoiceService {
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* When both discount and discountPercent are provided, discountPercent wins
|
||||
* and discount is derived from the line subtotal.
|
||||
*/
|
||||
private resolveItemDiscount(
|
||||
unitPrice: number,
|
||||
quantity: number,
|
||||
item: Pick<CreateInvoiceItemDto, 'discount' | 'discountPercent'>,
|
||||
): ResolvedItemDiscount {
|
||||
const subTotal = unitPrice * quantity;
|
||||
|
||||
if (item.discountPercent != null) {
|
||||
const discount = Math.round((subTotal * item.discountPercent) / 100);
|
||||
return {
|
||||
discount: discount > 0 ? discount : undefined,
|
||||
discountPercent: item.discountPercent,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.discount != null) {
|
||||
return {
|
||||
discount: item.discount > 0 ? item.discount : undefined,
|
||||
discountPercent: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
async create(dto: CreateInvoiceDto) {
|
||||
let user: User;
|
||||
@@ -75,17 +108,19 @@ export class InvoiceService {
|
||||
|
||||
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 { discount, discountPercent } = this.resolveItemDiscount(
|
||||
item.unitPrice,
|
||||
item.quantity,
|
||||
item,
|
||||
);
|
||||
|
||||
const invoiceItem = em.create(InvoiceItem, {
|
||||
invoice,
|
||||
product,
|
||||
quantity: item.quantity,
|
||||
unitPrice: unitPrice,
|
||||
discount: itemDiscount || undefined,
|
||||
unitPrice: item.unitPrice,
|
||||
discount,
|
||||
discountPercent,
|
||||
description: item.description,
|
||||
});
|
||||
invoice.items.add(invoiceItem);
|
||||
@@ -219,7 +254,18 @@ export class InvoiceService {
|
||||
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.discount !== undefined ||
|
||||
item.discountPercent !== undefined
|
||||
) {
|
||||
const { discount, discountPercent } = this.resolveItemDiscount(
|
||||
item.unitPrice ?? existingItem.unitPrice,
|
||||
item.quantity ?? existingItem.quantity,
|
||||
item,
|
||||
);
|
||||
existingItem.discount = discount;
|
||||
existingItem.discountPercent = discountPercent;
|
||||
}
|
||||
if (item.description !== undefined)
|
||||
existingItem.description = item.description;
|
||||
} else {
|
||||
@@ -233,13 +279,18 @@ export class InvoiceService {
|
||||
);
|
||||
}
|
||||
const product = await this.productService.findOneOrFail(item.productId);
|
||||
// const itemDiscount = item.discount ?? 0;
|
||||
const { discount, discountPercent } = this.resolveItemDiscount(
|
||||
item.unitPrice,
|
||||
item.quantity,
|
||||
item,
|
||||
);
|
||||
const invoiceItem = this.em.create(InvoiceItem, {
|
||||
invoice,
|
||||
product,
|
||||
quantity: item.quantity,
|
||||
unitPrice: item.unitPrice,
|
||||
// discount: itemDiscount || undefined,`
|
||||
discount,
|
||||
discountPercent,
|
||||
description: item.description,
|
||||
});
|
||||
invoice.items.add(invoiceItem);
|
||||
|
||||
@@ -20,7 +20,7 @@ export class CreditTransaction extends BaseEntity {
|
||||
@Property({ type: 'decimal', precision: 10, scale: 0 })
|
||||
balance!: number;
|
||||
|
||||
@Enum(() => CreditTransaction)
|
||||
@Enum(() => CreditTransactionType)
|
||||
type!: CreditTransactionType;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user