up
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-07 18:56:57 +03:30
parent ad4c7323f4
commit e242730cba
6 changed files with 133 additions and 18 deletions
+15 -2
View File
@@ -112,6 +112,7 @@ export class InvoiceService {
// let subTotal = 0;
for (const item of dto.items) {
const product = await this.productService.findOneOrFail(item.productId);
const { discount, discountPercent } = this.resolveItemDiscount(
@@ -131,7 +132,7 @@ export class InvoiceService {
});
invoice.items.add(invoiceItem);
}
if (dto.requestId) {
await this.requestService.hardDeleteRequestAndItems(dto.requestId, em);
@@ -140,13 +141,25 @@ export class InvoiceService {
await em.flush();
return invoice;
});
const allItems = invoice.items.getItems();
let subTotal = 0;
let discount = 0;
for (const item of allItems) {
subTotal += item.unitPrice * item.quantity;
discount += item.discount ?? 0;
}
const taxableBase = Math.max(0, subTotal - discount);
const taxAmount = enableTax ? Math.round(taxableBase * TAX_RATE) : 0;
const total = taxableBase + taxAmount;
// TODO : maybe it is needed to get a fresh instance.
this.eventEmitter.emit(
InvoiceCreatedEvent.name,
new InvoiceCreatedEvent(
invoice.id,
String(invoice.invoiceNumber),
invoice.total!,
total,
user.id,
),
);
@@ -85,11 +85,23 @@ export class ProductController {
/* =============================== PRODUCT ================================= */
@Get('public/categories')
@UseGuards(AuthGuard)
@ApiOperation({ summary: 'Get list of categories' })
async findAllCategoriesAsUser() {
return this.categoryService.findAll({ limit: 1000, isActive: true });
}
@Get('public/products')
@UseGuards(AuthGuard)
@ApiOperation({ summary: 'Get list of products' })
async findAllAsUser() {
const result = await this.productService.findAll({ limit: 1000 });
@ApiQuery({ name: 'categoryId', required: false, type: String })
async findAllAsUser(@Query() dto: FindproductsDto) {
const result = await this.productService.findAll({
limit: 1000,
isActive: true,
categoryId: dto.categoryId,
});
return result;
}
@@ -144,5 +156,14 @@ export class ProductController {
return this.productService.remove(id);
}
@Post('admin/products/:id/duplicate')
@UseGuards(AdminAuthGuard)
@Permissions(PermissionEnum.MANAGE_PRODUCTS)
@ApiOperation({ summary: 'Duplicate a product with all fields' })
@ApiParam({ name: 'id', required: true })
duplicate(@Param('id') id: string) {
return this.productService.duplicate(id);
}
}
@@ -7,6 +7,10 @@ import { RequiredEntityData } from '@mikro-orm/core';
import { Product } from '../entities/product.entity';
import { CategoryMessage, productMessage } from 'src/common/enums/message.enum';
import { CategoryRepository } from '../repositories/category.repository';
import { FieldRepository } from 'src/modules/form-builder/repository/field.repository';
import { FieldOption } from 'src/modules/form-builder/entities/field-option.entity';
import { Field } from 'src/modules/form-builder/entities/field.entity';
import { EntityType } from 'src/modules/form-builder/interface/print';
@Injectable()
export class ProductService {
@@ -14,6 +18,7 @@ export class ProductService {
constructor(
private readonly productRepository: ProductRepository,
private readonly categoryRepository: CategoryRepository,
private readonly fieldRepository: FieldRepository,
private readonly em: EntityManager,
) { }
@@ -103,4 +108,57 @@ export class ProductService {
if (!product) throw new NotFoundException(productMessage.NOT_FOUND);
return product;
}
async duplicate(productId: string) {
const product = await this.productRepository.findOne(
{ id: productId },
{ populate: ['category'] },
);
if (!product) {
throw new NotFoundException(productMessage.NOT_FOUND);
}
const fields = await this.fieldRepository.find(
{ entityId: productId, entityType: EntityType.product },
{ populate: ['options'], orderBy: { order: 'ASC' } },
);
return this.em.transactional(async (em) => {
const newProduct = em.create(Product, {
category: product.category,
title: `${product.title} - کپی`,
desc: product.desc,
linkUrl: product.linkUrl,
isActive: product.isActive,
images: product.images ? [...product.images] : undefined,
order: product.order,
});
em.persist(newProduct);
for (const field of fields) {
const newField = em.create(Field, {
entityType: EntityType.product,
entityId: newProduct.id,
name: field.name,
type: field.type,
isRequired: field.isRequired,
multiple: field.multiple,
order: field.order,
});
em.persist(newField);
for (const option of field.options.getItems()) {
const newOption = em.create(FieldOption, {
value: option.value,
order: option.order,
field: newField,
});
em.persist(newOption);
}
}
await em.flush();
return newProduct;
});
}
}
@@ -20,12 +20,36 @@ export class CategoryRepository extends EntityRepository<Category> {
super(em, Category);
}
private buildCategoryTree(categories: Category[]): Category[] {
const roots: Category[] = [];
for (const category of categories) {
category.children.removeAll();
}
for (const category of categories) {
const parentId = category.parent?.id;
if (parentId) {
const parent = categories.find((item) => item.id === parentId);
if (parent) {
parent.children.add(category);
continue;
}
}
roots.push(category);
}
return roots;
}
async findAllPaginated(opts: FindCategoriesOpts = {}): Promise<PaginatedResult<Category>> {
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc', isActive } = opts;
const offset = (page - 1) * limit;
const where: FilterQuery<Category> = { parent: null };
const where: FilterQuery<Category> = {};
if (typeof isActive === 'boolean') {
where.isActive = isActive;
@@ -37,13 +61,14 @@ export class CategoryRepository extends EntityRepository<Category> {
]
}
const [data, total] = await this.findAndCount(where, {
limit,
offset,
const allCategories = await this.find(where, {
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
populate: ['children']
populate: ['parent'],
});
const roots = this.buildCategoryTree(allCategories);
const data = roots.slice(offset, offset + limit);
const total = roots.length;
const totalPages = Math.ceil(total / limit);
return {
@@ -55,7 +80,5 @@ export class CategoryRepository extends EntityRepository<Category> {
totalPages,
},
};
}
}
@@ -45,7 +45,7 @@ export class ProductRepository extends EntityRepository<Product> {
limit,
offset,
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
populate: [],
populate: ['category'],
});
const totalPages = Math.ceil(total / limit);
+6 -6
View File
@@ -13,13 +13,13 @@ export const adminsData: AdminData[] = [
roleName: 'admin',
},
// {
// phone: '09121724095',
// firstName: 'آقای',
// lastName: 'نواعتقاد',
// roleName: 'admin',
{
phone: '09121724095',
firstName: 'آقای',
lastName: 'نواعتقاد',
roleName: 'admin',
// },
},
// {
// phone: '09185290775',
// firstName: 'حمید',