@@ -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);
|
||||
|
||||
@@ -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: 'حمید',
|
||||
|
||||
Reference in New Issue
Block a user