diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 9fe16e9025..2310c8d538 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -63,6 +63,7 @@ import { CandidateCvModule } from './candidate-cv/candidate-cv.module'; import { CandidateEducationModule } from './candidate-education/candidate-education.module'; import { CandidateSourceModule } from './candidate_source/candidate_source.module'; import { CandidateExperienceModule } from './candidate-experience/candidate-experience.module'; +import { ProductVariantSettingsModule } from './product-settings/product-settings.module'; @Module({ imports: [ @@ -242,6 +243,10 @@ import { CandidateExperienceModule } from './candidate-experience/candidate-expe { path: '/product-variants', module: ProductVariantModule + }, + { + path: '/product-variant-settings', + module: ProductVariantSettingsModule } ] } @@ -314,6 +319,7 @@ import { CandidateExperienceModule } from './candidate-experience/candidate-expe IntegrationMapModule, ProductVariantPriceModule, ProductVariantModule, + ProductVariantSettingsModule, IntegrationEntitySettingModule, IntegrationEntitySettingTiedEntityModule ], diff --git a/apps/api/src/app/product-option/product-option.entity.ts b/apps/api/src/app/product-option/product-option.entity.ts index 30c11038fb..45c3b31183 100644 --- a/apps/api/src/app/product-option/product-option.entity.ts +++ b/apps/api/src/app/product-option/product-option.entity.ts @@ -19,7 +19,8 @@ export class ProductOption extends Base implements IProductOption { @ManyToOne( () => Product, - (product) => product.options + (product) => product.options, + { onUpdate: 'CASCADE', onDelete: 'CASCADE' } ) @JoinColumn() product: Product; diff --git a/apps/api/src/app/product-option/product-option.service.ts b/apps/api/src/app/product-option/product-option.service.ts index f879677847..32a181834a 100644 --- a/apps/api/src/app/product-option/product-option.service.ts +++ b/apps/api/src/app/product-option/product-option.service.ts @@ -16,6 +16,6 @@ export class ProductOptionService extends CrudService { async createBulk( productOptionsInput: ProductOption[] ): Promise { - return this.productOptionRepository.create(productOptionsInput); + return this.productOptionRepository.save(productOptionsInput); } } diff --git a/apps/api/src/app/product-variant/product-variant.controller.ts b/apps/api/src/app/product-variant/product-variant.controller.ts index 4c85364fff..b3cb16b6b2 100644 --- a/apps/api/src/app/product-variant/product-variant.controller.ts +++ b/apps/api/src/app/product-variant/product-variant.controller.ts @@ -1,6 +1,15 @@ -import { Controller, HttpStatus, Post, Body } from '@nestjs/common'; +import { + Controller, + HttpStatus, + Post, + Body, + Get, + HttpCode, + Put, + Param +} from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; -import { CrudController } from '../core'; +import { CrudController, IPagination } from '../core'; import { ProductVariant } from './product-variant.entity'; import { ProductVariantService } from './product-variant.service'; import { ProductVariantCreateCommand } from './commands'; @@ -36,4 +45,44 @@ export class ProductVariantController extends CrudController { ): Promise { return this.commandBus.execute(new ProductVariantCreateCommand(entity)); } + + @ApiOperation({ + summary: 'Find all product variants' + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Found product variants', + type: Product + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Record not found' + }) + @Get('all') + async findAllProductVariants(): Promise> { + return this.productVariantService.findAllProductVariants(); + } + + @ApiOperation({ summary: 'Update an existing record' }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'The record has been successfully edited.' + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Record not found' + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: + 'Invalid input, The response body may contain clues as to what went wrong' + }) + @HttpCode(HttpStatus.ACCEPTED) + @Put(':id') + async update( + @Param('id') id: string, + @Body() productVariant: ProductVariant + ): Promise { + return this.productVariantService.updateVariant(productVariant); + } } diff --git a/apps/api/src/app/product-variant/product-variant.entity.ts b/apps/api/src/app/product-variant/product-variant.entity.ts index 82faa29020..a9ad25b15f 100644 --- a/apps/api/src/app/product-variant/product-variant.entity.ts +++ b/apps/api/src/app/product-variant/product-variant.entity.ts @@ -61,21 +61,30 @@ export class ProductVariant extends Base implements IProductVariant { @OneToOne( () => ProductVariantSettings, - (settings) => settings.productVariant + (settings) => settings.productVariant, + { + eager: true, + onDelete: 'CASCADE' + } ) @JoinColumn() settings: ProductVariantSettings; @OneToOne( () => ProductVariantPrice, - (variantPrice) => variantPrice.productVariant + (variantPrice) => variantPrice.productVariant, + { + eager: true, + onDelete: 'CASCADE' + } ) @JoinColumn() price: ProductVariantPrice; @ManyToOne( () => Product, - (product) => product.variants + (product) => product.variants, + { onDelete: 'CASCADE' } ) @JoinColumn() product: Product; diff --git a/apps/api/src/app/product-variant/product-variant.service.ts b/apps/api/src/app/product-variant/product-variant.service.ts index 54b633688f..75c16bc5ef 100644 --- a/apps/api/src/app/product-variant/product-variant.service.ts +++ b/apps/api/src/app/product-variant/product-variant.service.ts @@ -1,5 +1,5 @@ import { Repository } from 'typeorm'; -import { CrudService } from '../core'; +import { CrudService, IPagination } from '../core'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { ProductVariant } from './product-variant.entity'; @@ -13,6 +13,15 @@ export class ProductVariantService extends CrudService { super(productVariantRepository); } + async findAllProductVariants(): Promise> { + const total = await this.productVariantRepository.count(); + const items = await this.productVariantRepository.find({ + relations: ['settings', 'price'] + }); + + return { items, total }; + } + async createBulk( productVariants: ProductVariant[] ): Promise { @@ -24,4 +33,10 @@ export class ProductVariantService extends CrudService { ): Promise { return this.productVariantRepository.save(productVariant); } + + async updateVariant( + productVariant: ProductVariant + ): Promise { + return this.productVariantRepository.save(productVariant); + } } diff --git a/apps/api/src/app/product/commands/handlers/product.create.handler.ts b/apps/api/src/app/product/commands/handlers/product.create.handler.ts index 608a1a8642..b31e60b02d 100644 --- a/apps/api/src/app/product/commands/handlers/product.create.handler.ts +++ b/apps/api/src/app/product/commands/handlers/product.create.handler.ts @@ -18,14 +18,15 @@ export class ProductCreateHandler const product = await this.productService.create(productInput); - const options = productInput.options.map((optionInput) => { + const optionsInput = productInput.options.map((optionInput) => { const option = new ProductOption(); Object.assign(option, { ...optionInput, product }); return option; }); - product.options = await this.productOptionService.createBulk(options); + await this.productOptionService.createBulk(optionsInput); + return product; } } diff --git a/apps/api/src/app/product/commands/handlers/product.update.handler.ts b/apps/api/src/app/product/commands/handlers/product.update.handler.ts new file mode 100644 index 0000000000..d83df82e4a --- /dev/null +++ b/apps/api/src/app/product/commands/handlers/product.update.handler.ts @@ -0,0 +1,26 @@ +import { ICommandHandler, CommandHandler } from '@nestjs/cqrs'; +import { ProductService } from '../../../product/product.service'; +import { ProductOptionService } from '../../../product-option/product-option.service'; +import { Product } from '../../product.entity'; +import { ProductUpdateCommand } from '../product.update.command'; + +@CommandHandler(ProductUpdateCommand) +export class ProductUpdateHandler + implements ICommandHandler { + constructor( + private productOptionService: ProductOptionService, + private productService: ProductService + ) {} + + public async execute(command?: ProductUpdateCommand): Promise { + const { productUpdateRequest } = command; + + const product = await this.productService.saveProduct( + productUpdateRequest + ); + + //todo manage options + + return product; + } +} diff --git a/apps/api/src/app/product/commands/product.update.command.ts b/apps/api/src/app/product/commands/product.update.command.ts new file mode 100644 index 0000000000..0d3c320601 --- /dev/null +++ b/apps/api/src/app/product/commands/product.update.command.ts @@ -0,0 +1,11 @@ +import { ICommand } from '@nestjs/cqrs'; +import { Product } from '../../product/product.entity'; + +export class ProductUpdateCommand implements ICommand { + static readonly type = '[Product] Update'; + + constructor( + public readonly id: string, + public readonly productUpdateRequest: Product + ) {} +} diff --git a/apps/api/src/app/product/product.controller.ts b/apps/api/src/app/product/product.controller.ts index 9fd8b66bab..5f3f7a9f1c 100644 --- a/apps/api/src/app/product/product.controller.ts +++ b/apps/api/src/app/product/product.controller.ts @@ -3,7 +3,10 @@ import { Get, HttpStatus, Post, - Body + Body, + Put, + Param, + HttpCode // UseGuards } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; @@ -12,6 +15,7 @@ import { ProductService } from './product.service'; import { Product } from './product.entity'; import { CommandBus } from '@nestjs/cqrs'; import { ProductCreateCommand } from './commands/product.create.command'; +import { ProductUpdateCommand } from './commands/product.update.command'; // import { PermissionGuard } from '../shared/guards/auth/permission.guard'; @ApiTags('Product') @@ -56,4 +60,27 @@ export class ProductController extends CrudController { async createProduct(@Body() entity: Product, ...options: any[]) { return this.commandBus.execute(new ProductCreateCommand(entity)); } + + @ApiOperation({ summary: 'Update an existing record' }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'The record has been successfully edited.' + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Record not found' + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: + 'Invalid input, The response body may contain clues as to what went wrong' + }) + @HttpCode(HttpStatus.ACCEPTED) + @Put(':id') + async update( + @Param('id') id: string, + @Body() entity: Product + ): Promise { + return this.commandBus.execute(new ProductUpdateCommand(id, entity)); + } } diff --git a/apps/api/src/app/product/product.entity.ts b/apps/api/src/app/product/product.entity.ts index 8325bc8225..98fb29b62f 100644 --- a/apps/api/src/app/product/product.entity.ts +++ b/apps/api/src/app/product/product.entity.ts @@ -38,7 +38,8 @@ export class Product extends Base implements IProduct { @OneToMany( () => ProductVariant, - (productVariant) => productVariant.product + (productVariant) => productVariant.product, + { onDelete: 'CASCADE' } ) variants: ProductVariant[]; @@ -62,5 +63,5 @@ export class Product extends Base implements IProduct { (type) => ProductOption, (productOption) => productOption.product ) - options?: ProductOption[]; + options: ProductOption[]; } diff --git a/apps/api/src/app/product/product.module.ts b/apps/api/src/app/product/product.module.ts index 35a3076152..b1519a8ffe 100644 --- a/apps/api/src/app/product/product.module.ts +++ b/apps/api/src/app/product/product.module.ts @@ -7,11 +7,17 @@ import { ProductOption } from '../product-option/product-option.entity'; import { CqrsModule } from '@nestjs/cqrs'; import { ProductOptionService } from '../product-option/product-option.service'; import { ProductCreateHandler } from './commands/handlers/product.create.handler'; +import { ProductUpdateHandler } from './commands/handlers/product.update.handler'; @Module({ imports: [TypeOrmModule.forFeature([Product, ProductOption]), CqrsModule], controllers: [ProductController], - providers: [ProductService, ProductOptionService, ProductCreateHandler], + providers: [ + ProductService, + ProductOptionService, + ProductCreateHandler, + ProductUpdateHandler + ], exports: [ProductService] }) export class ProductModule {} diff --git a/apps/api/src/app/product/product.service.ts b/apps/api/src/app/product/product.service.ts index d1c5bd0594..126aeec449 100644 --- a/apps/api/src/app/product/product.service.ts +++ b/apps/api/src/app/product/product.service.ts @@ -21,4 +21,8 @@ export class ProductService extends CrudService { return { items, total }; } + + async saveProduct(productRequest: Product): Promise { + return await this.productRepository.save(productRequest); + } } diff --git a/apps/gauzy/src/app/@core/services/product-variant-price.service.ts b/apps/gauzy/src/app/@core/services/product-variant-price.service.ts new file mode 100644 index 0000000000..c70c17cd23 --- /dev/null +++ b/apps/gauzy/src/app/@core/services/product-variant-price.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ProductVariantPrice } from '@gauzy/models'; +import { first } from 'rxjs/operators'; + +@Injectable() +export class ProductVariantPriceService { + PRODUCT_VARIANT_PRICE_URL = '/api/product-variant-prices'; + + constructor(private http: HttpClient) {} + + updateProductVariantPrice( + productVariantPrice: ProductVariantPrice + ): Promise { + return this.http + .put( + `${this.PRODUCT_VARIANT_PRICE_URL}/${productVariantPrice.id}`, + productVariantPrice + ) + .pipe(first()) + .toPromise(); + } +} diff --git a/apps/gauzy/src/app/@core/services/product-variant-settings.service.ts b/apps/gauzy/src/app/@core/services/product-variant-settings.service.ts new file mode 100644 index 0000000000..b6213d6212 --- /dev/null +++ b/apps/gauzy/src/app/@core/services/product-variant-settings.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ProductVariantSettings } from '@gauzy/models'; +import { first } from 'rxjs/operators'; + +@Injectable() +export class ProductVariantSettingsService { + PRODUCT_VARIANT_SETTINGS_URL = '/api/product-variant-settings'; + + constructor(private http: HttpClient) {} + + updateProductVariantSettings( + productVariantSettings: ProductVariantSettings + ): Promise { + return this.http + .put( + `${this.PRODUCT_VARIANT_SETTINGS_URL}/${productVariantSettings.id}`, + productVariantSettings + ) + .pipe(first()) + .toPromise(); + } + + getProductVariantSettings(): Promise { + return this.http + .get(this.PRODUCT_VARIANT_SETTINGS_URL) + .pipe(first()) + .toPromise(); + } +} diff --git a/apps/gauzy/src/app/@core/services/product-variant.service.ts b/apps/gauzy/src/app/@core/services/product-variant.service.ts index 345b021855..166f2f332f 100644 --- a/apps/gauzy/src/app/@core/services/product-variant.service.ts +++ b/apps/gauzy/src/app/@core/services/product-variant.service.ts @@ -9,6 +9,13 @@ export class ProductVariantService { constructor(private http: HttpClient) {} + getProductVariant(id: string): Promise { + return this.http + .get(`${this.PRODUCT_VARIANTS_URL}/${id}`) + .pipe(first()) + .toPromise(); + } + createProductVariants(product: Product): Promise { return this.http .post( @@ -18,4 +25,16 @@ export class ProductVariantService { .pipe(first()) .toPromise(); } + + updateProductVariant( + productVariant: ProductVariant + ): Promise { + return this.http + .put( + `${this.PRODUCT_VARIANTS_URL}/${productVariant.id}`, + productVariant + ) + .pipe(first()) + .toPromise(); + } } diff --git a/apps/gauzy/src/app/@shared/product-mutation/product-form/product-form.component.html b/apps/gauzy/src/app/@shared/product-mutation/product-form/product-form.component.html index 541cd4294a..462df3f406 100644 --- a/apps/gauzy/src/app/@shared/product-mutation/product-form/product-form.component.html +++ b/apps/gauzy/src/app/@shared/product-mutation/product-form/product-form.component.html @@ -175,7 +175,7 @@