This commit is contained in:
2026-01-25 12:36:56 +03:30
parent 2449ac447d
commit a7bd26c7ca
9 changed files with 197 additions and 24 deletions
@@ -70,12 +70,12 @@ export class OrderController {
// return this.orderService.addOrderItem(orderId, body); // return this.orderService.addOrderItem(orderId, body);
// } // }
@Post('admin/orders/:orderId/create-invoice') // @Post('admin/orders/:orderId/create-invoice')
@UseGuards(AuthGuard) // @UseGuards(AuthGuard)
@ApiOperation({ summary: 'Create invoice for new order' }) // @ApiOperation({ summary: 'Create invoice for new order' })
createInvoice(@Param('orderId') orderId: string, @Body() body: CreateInvoiceDto) { // createInvoice(@Param('orderId') orderId: string, @Body() body: CreateInvoiceDto) {
return this.orderService.createInvoice(orderId, body); // return this.orderService.calculateOrder(orderId, body);
} // }
@Post('admin/orders/:orderId/assign-designer') @Post('admin/orders/:orderId/assign-designer')
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@@ -0,0 +1,91 @@
import {
IsArray, IsNumber,
IsNotEmpty,
ArrayMinSize,
IsBoolean,
IsString
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
export class InvoiceOrderItemDto {
@IsNumber()
@ApiProperty()
orderItemId: number;
@ApiProperty()
@IsNumber()
unitPrice: number
@ApiProperty()
@IsNumber()
discount: number
@ApiProperty()
@IsNumber()
quantity: number
}
export class InvoiceOrderNewItemDto {
@IsNumber()
@ApiProperty()
productId: number;
@ApiProperty()
@IsArray()
attributesValues: string[]
@ApiProperty()
@IsNumber()
quantity: number
@ApiProperty()
@IsNumber()
unitPrice: number
@ApiProperty()
@IsNumber()
discount: number
}
export class UpdateInvoicedOrderDto {
@IsArray()
@ApiProperty({
isArray: true, type: [InvoiceOrderItemDto], example: [
{
orderItemId: 1,
unitPrice: 100,
}
]
})
@IsNotEmpty()
@ArrayMinSize(1, { message: 'At least one product is required' })
@Type(() => InvoiceOrderItemDto)
items: InvoiceOrderItemDto[];
@IsArray()
@ApiProperty({
isArray: true, type: [InvoiceOrderNewItemDto], example: [
{
orderItemId: 1,
unitPrice: 100,
}
]
})
@Type(() => InvoiceOrderNewItemDto)
newItems: InvoiceOrderNewItemDto[];
@ApiProperty()
@IsBoolean()
enableTax: boolean
@ApiPropertyOptional({ example: [] })
@IsArray()
@IsString({ each: true })
attachments: string[]
}
@@ -1,5 +1 @@
// import { PartialType } from '@nestjs/swagger';
// import { CreateOrderDto } from './create-order.dto';
// export class UpdateOrderDto extends PartialType(CreateOrderDto) {}
export class UpdateOrderDto {} export class UpdateOrderDto {}
@@ -26,14 +26,16 @@ export class OrderItem extends BaseEntity {
@Property({ type: 'decimal', precision: 10, scale: 0 }) @Property({ type: 'decimal', precision: 10, scale: 0 })
unitPrice!: number; unitPrice!: number;
@Property({ type: 'decimal', precision: 10, scale: 0 }) get subTotal(): number {
subTotal!: number; return Number(this.unitPrice) * (this.quantity)
}
@Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 })
discount: number = 0; discount: number = 0;
@Property({ type: 'decimal', precision: 10, scale: 0 }) get total(): number {
total!: number; return Number(this.subTotal) - Number(this.discount || 0)
}
@Property({ nullable: true }) @Property({ nullable: true })
description?: string; description?: string;
@@ -102,6 +102,12 @@ export class Order extends BaseEntity {
@Property({ type: 'string', nullable: true }) @Property({ type: 'string', nullable: true })
paymentMethod?: string; paymentMethod?: string;
@Property({ nullable: true, columnType: 'timestamptz' })
invoicedAt?: Date;
@Property({ type: 'boolean', default: false })
enableTax?: Boolean = false
@BeforeCreate() @BeforeCreate()
async generateOrderNumber(args: EventArgs<Order>) { async generateOrderNumber(args: EventArgs<Order>) {
const em = args.em; const em = args.em;
+16 -3
View File
@@ -1,15 +1,28 @@
export enum OrderStatusEnum { export enum OrderStatusEnum {
DRAFT = 'draft', CREATED = 'created',
INVOICED = 'invoiced', INVOICED = 'invoiced',
WAITING_to_CONFIRM_INVOICE = 'waiting_to_confirm_invoice',
IN_DESIGN = 'in_design',
DESIGN_INITIATED = 'design_initiated',
DESIGN_FINISHED = 'design_finished',
DESIGN_REVISION = 'design_revision',
READY_TO_PRINTING = 'ready_to_printing',
IN_PRINTING = 'in_printing', IN_PRINTING = 'in_printing',
READY_FOR_DELIVERY = 'ready_for_delivery',
DELIVERED = 'delivered',
AFTER_PRINT_COVER = 'after_print_cover',
AFTER_PRINT_BOXING = 'after_print_boxing',
AFTER_PRINT_CARTONING = 'after_print_cartoning',
FINISHED = 'finished', FINISHED = 'finished',
CANCELED = 'canceled', CANCELED = 'canceled',
} }
+60 -6
View File
@@ -15,7 +15,7 @@ import { TicketRepository } from 'src/modules/ticket/repositories/tickets.reposi
import { CreateInvoiceDto } from '../dto/create-invoice.dto'; import { CreateInvoiceDto } from '../dto/create-invoice.dto';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository'; import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { CreateOrderAsAdminDto } from '../dto/create-order-as-admin.dto'; import { CreateOrderAsAdminDto } from '../dto/create-order-as-admin.dto';
@Injectable() @Injectable()
export class OrderService { export class OrderService {
@@ -52,8 +52,9 @@ export class OrderService {
total: 0, total: 0,
paidAmount: 0, paidAmount: 0,
balance: 0, balance: 0,
status: OrderStatusEnum.DRAFT, status: OrderStatusEnum.CREATED,
taxAmount: 0, taxAmount: 0,
enableTax: false
}) })
em.persist(order) em.persist(order)
@@ -198,7 +199,47 @@ export class OrderService {
return order return order
} }
async createInvoice(orderId: string, dto: CreateInvoiceDto) { async calculateOrder(orderId: string, em: EntityManager) {
const targetOrder = await this.findOneOrFail(orderId)
const order = await this.em.transactional(async (em) => {
// calculate order financials
let subTotal = 0
let totalDiscount = 0
// TODO : use reduce
for (let orderItem of targetOrder.items) {
subTotal += orderItem.total
totalDiscount += orderItem.discount
}
const totalBeforeTax = subTotal - totalDiscount
let tax = 0
if (targetOrder.enableTax) {
tax = 0.1 * totalBeforeTax
}
const total = totalBeforeTax + tax
// Update Order financial values
targetOrder.subTotal = subTotal
targetOrder.discount = totalDiscount
targetOrder.taxAmount = tax
targetOrder.total = total
em.persist(targetOrder)
return targetOrder
})
return order
}
async updateOrder(orderId: string, dto: CreateInvoiceDto) {
const { items, enableTax, attachments, newItems } = dto const { items, enableTax, attachments, newItems } = dto
const targetOrder = await this.findOneOrFail(orderId) const targetOrder = await this.findOneOrFail(orderId)
@@ -222,7 +263,7 @@ export class OrderService {
} }
// TODO use Deciaml js to calculation // TODO use Deciaml js to calculation
orderItem.unitPrice = found.unitPrice orderItem.unitPrice = found.unitPrice
orderItem.subTotal = (found.unitPrice) * orderItem.quantity // orderItem.subTotal = (found.unitPrice) * orderItem.quantity
subTotal += orderItem.subTotal subTotal += orderItem.subTotal
@@ -279,6 +320,7 @@ export class OrderService {
// change status // change status
targetOrder.status = OrderStatusEnum.INVOICED targetOrder.status = OrderStatusEnum.INVOICED
targetOrder.invoicedAt = new Date()
em.persist(targetOrder) em.persist(targetOrder)
@@ -321,7 +363,7 @@ export class OrderService {
} }
order.designer = designer order.designer = designer
order.status = OrderStatusEnum.IN_DESIGN // order.status = OrderStatusEnum.IN_DESIGN
await this.em.persistAndFlush(order) await this.em.persistAndFlush(order)
@@ -335,7 +377,7 @@ export class OrderService {
if (!order) { if (!order) {
throw new BadRequestException("Order not found") throw new BadRequestException("Order not found")
} }
if (![OrderStatusEnum.DRAFT, OrderStatusEnum.INVOICED].includes(order.status)) { if (![OrderStatusEnum.CREATED, OrderStatusEnum.INVOICED].includes(order.status)) {
throw new BadRequestException("Order status must be of of Drfat or Invoiced") throw new BadRequestException("Order status must be of of Drfat or Invoiced")
} }
if (order.payments.length > 0) { if (order.payments.length > 0) {
@@ -352,6 +394,18 @@ export class OrderService {
} }
async updateStatus(orderId: string, newStatus: OrderStatusEnum) {
const order = await this.orderRepository.findOne({ id: orderId })
if (!order) {
throw new BadRequestException("Order not found")
}
order.status = newStatus
await this.em.flush()
return order
}
// async addOrderItem(orderId: string, dto: AddOrderItemDto) { // async addOrderItem(orderId: string, dto: AddOrderItemDto) {
// const order = await this.orderRepository.findOne({ id: orderId }) // const order = await this.orderRepository.findOne({ id: orderId })
// if (!order) { // if (!order) {
@@ -99,6 +99,16 @@ export class productController {
} }
/* =============================== PRODUCT ================================= */ /* =============================== PRODUCT ================================= */
@Get('public/products')
// @UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@Permissions(Permission.MANAGE_PRODUCTS)
@ApiOperation({ summary: 'Get list of products' })
async findAllAsUser() {
const result = await this.productService.findAll({ limit: 1000 });
return result;
}
@Post('admin/product') @Post('admin/product')
// @UseGuards(AdminAuthGuard) // @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@@ -160,6 +170,7 @@ export class productController {
/*=============================== Attibute ==========================*/ /*=============================== Attibute ==========================*/
@Post('admin/product/:id/attribute') @Post('admin/product/:id/attribute')
// @UseGuards(AdminAuthGuard) // @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@@ -86,7 +86,7 @@ export class AttributeService {
} }
async remove(id: number): Promise<void> { async remove(id: number): Promise<void> {
const attribute = await this.findById(id); const attribute = await this.findById(id);
if (!attribute) { if (!attribute) {
throw new NotFoundException('attribute not found'); throw new NotFoundException('attribute not found');
} }