From 8b7d48779eb987bd70dd5d9141f2f289bccd2f66 Mon Sep 17 00:00:00 2001 From: sanchezvem Date: Sat, 13 Dec 2025 15:50:59 +0100 Subject: [PATCH] added create quotation function and edit end delete product in quotation table --- .../create-quotation-form.css | 0 .../create-quotation-form.html | 46 +++++ .../create-quotation-form.ts | 62 +++++++ .../purchase-order-table.ts | 1 - .../quotation-table/quotation-table.html | 14 +- .../quotation-table/quotation-table.ts | 63 ++++++- src/app/pages/stock/stock.html | 11 +- src/app/pages/stock/stock.ts | 166 ++++++++++-------- src/app/services/api/.openapi-generator/FILES | 2 + .../services/api/api/quotations.service.ts | 66 +++++++ .../api/model/create-product-quotation-dto.ts | 16 ++ .../api/model/create-quotation-dto.ts | 18 ++ .../api/model/create-quotation-product-dto.ts | 2 +- .../services/api/model/get-quotation-dto.ts | 2 +- .../api/model/get-quotation-product-dto.ts | 2 +- src/app/services/api/model/models.ts | 2 + 16 files changed, 392 insertions(+), 81 deletions(-) create mode 100644 src/app/components/create-quotation-form/create-quotation-form.css create mode 100644 src/app/components/create-quotation-form/create-quotation-form.html create mode 100644 src/app/components/create-quotation-form/create-quotation-form.ts create mode 100644 src/app/services/api/model/create-product-quotation-dto.ts create mode 100644 src/app/services/api/model/create-quotation-dto.ts diff --git a/src/app/components/create-quotation-form/create-quotation-form.css b/src/app/components/create-quotation-form/create-quotation-form.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/create-quotation-form/create-quotation-form.html b/src/app/components/create-quotation-form/create-quotation-form.html new file mode 100644 index 0000000..74e0fdb --- /dev/null +++ b/src/app/components/create-quotation-form/create-quotation-form.html @@ -0,0 +1,46 @@ +
+ + + Message + + + + + + + + + + Conditions générales + + + + + + + +
+ + + + Produit + Quantité + + + + @for (line of lines.controls.slice(); let i = $index; track i) { + + {{ line.value.name }} + + + + + + } + + +
+
diff --git a/src/app/components/create-quotation-form/create-quotation-form.ts b/src/app/components/create-quotation-form/create-quotation-form.ts new file mode 100644 index 0000000..52d5543 --- /dev/null +++ b/src/app/components/create-quotation-form/create-quotation-form.ts @@ -0,0 +1,62 @@ +import { Component } from '@angular/core'; +import { + FormArray, + FormBuilder, + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, + Validators +} from "@angular/forms"; +import {NzColDirective} from "ng-zorro-antd/grid"; +import {NzFlexDirective} from "ng-zorro-antd/flex"; +import {NzFormControlComponent, NzFormItemComponent, NzFormLabelComponent} from "ng-zorro-antd/form"; +import {NzInputDirective} from "ng-zorro-antd/input"; +import {NzInputNumberComponent} from "ng-zorro-antd/input-number"; +import {NzTableComponent} from "ng-zorro-antd/table"; +import {GetProductDto} from "../../services/api"; + +@Component({ + selector: 'app-create-quotation-form', + imports: [ + FormsModule, + NzColDirective, + NzFlexDirective, + NzFormControlComponent, + NzFormItemComponent, + NzFormLabelComponent, + NzInputDirective, + NzInputNumberComponent, + NzTableComponent, + ReactiveFormsModule + ], + templateUrl: './create-quotation-form.html', + styleUrl: './create-quotation-form.css', +}) +export class CreateQuotationForm { + createQuotationForm: FormGroup + + constructor(private fb: FormBuilder) { + this.createQuotationForm = this.fb.group({ + message: new FormControl(null, Validators.required), + purchaseConditions: new FormControl(null, Validators.required), + lines: this.fb.array([]) + }); + } + + get lines(): FormArray { + return this.createQuotationForm.get('lines') as FormArray; + } + + // Ajouter des produits sélectionnés dans le formulaire + syncSelectedProducts(selectedProducts: GetProductDto[]) { + this.lines.clear(); + selectedProducts.forEach(p => { + this.lines.push(this.fb.group({ + productId: [p.id], + name: [p.name], + quantity: [1, [Validators.required, Validators.min(1)]] + })); + }); + } +} diff --git a/src/app/components/purchase-order-table/purchase-order-table.ts b/src/app/components/purchase-order-table/purchase-order-table.ts index 1772417..49f4e3b 100644 --- a/src/app/components/purchase-order-table/purchase-order-table.ts +++ b/src/app/components/purchase-order-table/purchase-order-table.ts @@ -85,7 +85,6 @@ export class PurchaseOrderTable implements OnInit { ); this.fileService.downloadBlob(pdf) } catch (e) { - console.error(e); this.notificationService.error( 'Erreur', 'Impossible de générer un PDF' diff --git a/src/app/components/quotation-table/quotation-table.html b/src/app/components/quotation-table/quotation-table.html index 34309da..32787ab 100644 --- a/src/app/components/quotation-table/quotation-table.html +++ b/src/app/components/quotation-table/quotation-table.html @@ -32,15 +32,17 @@ - @for (product of quotation.getQuotationProductDto; track product.productId) { + @for (product of quotation.products; track product.productId) { {{ product.productReferences }} {{ product.productName }} Price ??? - Quantité ??? + {{ product.quantity }}
- + + +
@@ -70,4 +72,10 @@ + + + \ No newline at end of file diff --git a/src/app/components/quotation-table/quotation-table.ts b/src/app/components/quotation-table/quotation-table.ts index 5c63d21..eab961d 100644 --- a/src/app/components/quotation-table/quotation-table.ts +++ b/src/app/components/quotation-table/quotation-table.ts @@ -5,10 +5,16 @@ import {ModalNav} from "../modal-nav/modal-nav"; import {NzDividerComponent} from "ng-zorro-antd/divider"; import {NzIconDirective} from "ng-zorro-antd/icon"; import {QuotationForm} from "../quotation-form/quotation-form"; -import {GetQuotationDto, QuotationsService} from "../../services/api"; +import { + GetQuotationDto, + GetQuotationProductDto, + QuotationproductsService, + QuotationsService +} from "../../services/api"; import {NzNotificationService} from "ng-zorro-antd/notification"; import {firstValueFrom} from "rxjs"; import {FileService} from "../../services/file.service"; +import {QuantityForm} from "../quantity-form/quantity-form"; @Component({ selector: 'app-quotation-table', @@ -19,6 +25,7 @@ import {FileService} from "../../services/file.service"; NzIconDirective, NzTableComponent, QuotationForm, + QuantityForm, ], templateUrl: './quotation-table.html', styleUrl: './quotation-table.css', @@ -27,10 +34,12 @@ import {FileService} from "../../services/file.service"; export class QuotationTable implements OnInit { private quotationsService = inject(QuotationsService); private notificationService = inject(NzNotificationService); + private quotationProductsService = inject(QuotationproductsService) private fileService = inject(FileService); quotations = signal([]); quotationsLoading = signal(false); modal = viewChild.required('modalNav'); + modalQuantity = viewChild.required('modalQuantity'); async ngOnInit() { await this.fetchQuotations(); @@ -102,6 +111,43 @@ export class QuotationTable implements OnInit { } } + async deleteProduct(productId: number, quotationId: number) { + this.quotationsLoading.set(true) + try { + await firstValueFrom(this.quotationProductsService.deleteQuotationProductEndpoint(productId, quotationId)) + this.notificationService.success( + 'Success', + 'Suppression effectuée' + ) + } catch (e) { + this.notificationService.error( + 'Erreur', + 'Impossible de supprimer la ligne' + ) + } + this.quotationsLoading.set(false) + await this.fetchQuotations(); + } + + async editQuantity(productId: number, quotationId: number, updateQuantityComponent: QuantityForm) { + if (updateQuantityComponent.quantityForm.invalid) { + this.notificationService.error( + 'Erreur', + 'Erreur d\'écriture dans le formulaire' + ) + return; + } + + try { + const quantity = updateQuantityComponent.quantityForm.getRawValue(); + await firstValueFrom(this.quotationProductsService.patchQuotationProductQuantityEndpoint(productId, quotationId, quantity)) + + this.notificationService.success('Success', 'Quantité modifiée') + } catch (e) { + this.notificationService.error('Erreur', 'Erreur lors de la modification') + } + } + selectedQuotation: GetQuotationDto | null = null; openEditModal(quotation: GetQuotationDto) { this.selectedQuotation = { ...quotation }; @@ -120,5 +166,20 @@ export class QuotationTable implements OnInit { onModalCancel(modal: ModalNav) { modal.isVisible = false; } + + selectedQuantity: GetQuotationProductDto | null = null; + openEditQuantityModal(quantity: GetQuotationProductDto) { + this.selectedQuantity = { ...quantity }; + this.modalQuantity().showModal(); + } + + async onModalQuantityOk(productId: number, quotationId: number, updateQuantityComponent: QuantityForm, modal: ModalNav) { + if (!this.selectedQuantity) return; + + await this.editQuantity(productId, quotationId, updateQuantityComponent); + updateQuantityComponent.quantityForm.reset(); + modal.isVisible = false; + await this.fetchQuotations(); + } } diff --git a/src/app/pages/stock/stock.html b/src/app/pages/stock/stock.html index d44d876..f123e20 100644 --- a/src/app/pages/stock/stock.html +++ b/src/app/pages/stock/stock.html @@ -11,8 +11,15 @@ - - + + } diff --git a/src/app/pages/stock/stock.ts b/src/app/pages/stock/stock.ts index fa0a89a..4b53257 100644 --- a/src/app/pages/stock/stock.ts +++ b/src/app/pages/stock/stock.ts @@ -2,12 +2,12 @@ import {Component, inject, viewChild} from '@angular/core'; import {StockTable} from "../../components/stock-table/stock-table"; import {Search} from "../../components/search/search"; import {ModalButton} from "../../components/modal-button/modal-button"; -import {PurchaseOrderForm} from "../../components/purchase-order-form/purchase-order-form"; import {QuotationForm} from "../../components/quotation-form/quotation-form"; -import {ProductsService, PurchaseordersService} from "../../services/api"; +import {PurchaseordersService, QuotationsService} from "../../services/api"; import {NzNotificationService} from "ng-zorro-antd/notification"; import {firstValueFrom} from "rxjs"; import {CreatePurchaseorderForm} from "../../components/create-purchaseorder-form/create-purchaseorder-form"; +import {CreateQuotationForm} from "../../components/create-quotation-form/create-quotation-form"; @Component({ selector: 'app-stock', @@ -15,85 +15,22 @@ import {CreatePurchaseorderForm} from "../../components/create-purchaseorder-for StockTable, Search, ModalButton, - QuotationForm, CreatePurchaseorderForm, + CreateQuotationForm, ], templateUrl: './stock.html', styleUrl: './stock.css', }) export class Stock { - createQuotation = viewChild.required('quotationForm'); createPurchaseOrder = viewChild.required('purchaseOrderForm'); + createQuotation = viewChild.required('quotationForm'); productTable = viewChild.required('stockTable'); - private productsService = inject(ProductsService); private purchaseordersService = inject(PurchaseordersService) + private quotationsService = inject(QuotationsService) private notificationService = inject(NzNotificationService) modalButtonPurchaseOrder = viewChild.required('modalButtonPurchaseOrder'); - - onProductSearch(query: string) { - this.productTable().applySearch(query); - } - - async addPurchaseOrder() { - if (this.createPurchaseOrder().createPurchaseOrderForm.invalid) return; - - const orderLines = this.createPurchaseOrder().lines.value.map(line => ({ - productId: line.productId, - quantity: line.quantity - })); - - try { - const purchaseOrder = this.createPurchaseOrder().createPurchaseOrderForm.getRawValue(); - await firstValueFrom(this.purchaseordersService.createPurchaseOrder(purchaseOrder)); - this.notificationService.success('Succès', 'Bon de commande crée') - }catch (e) { - console.error(e); - this.notificationService.error('Erreur', 'Erreur lors de la création du bon de commande.') - } - } - - async onModalOk() { - const form = this.createPurchaseOrder().createPurchaseOrderForm; - - if (form.invalid) { - this.notificationService.error('Erreur', 'Formulaire invalide'); - return; - } - - const orderLines = this.createPurchaseOrder().lines.value.map(line => ({ - productId: line.productId, - quantity: line.quantity - })); - - if (orderLines.length === 0) { - this.notificationService.error('Erreur', 'Aucun produit sélectionné'); - return; - } - - const purchaseOrder = { - purchaseConditions: form.get('purchaseConditions')!.value, - products: orderLines - }; - console.log('DTO envoyé :', purchaseOrder); - try { - await firstValueFrom( - this.purchaseordersService.createPurchaseOrder(purchaseOrder) - ); - this.notificationService.success('Succès', 'Bon de commande créé'); - form.reset(); - this.modalButtonPurchaseOrder().isVisible = false; - await this.productTable().fetchProducts(); - - } catch (e) { - console.error(e); - this.notificationService.error('Erreur', 'Erreur lors de la création du bon de commande.'); - } - } - - onModalCancel() { - this.modalButtonPurchaseOrder().isVisible = false; - } + modalButtonQuotation = viewChild.required('modalButtonQuotation'); hasSelection = false; @@ -101,8 +38,48 @@ export class Stock { this.hasSelection = value; } - test(){ - console.log(this.productTable().selectedIds); + onProductSearch(query: string) { + this.productTable().applySearch(query); + } + + async addPurchaseOrder() { + const form = this.createPurchaseOrder().createPurchaseOrderForm; + if (form.invalid) { + this.notificationService.error('Erreur', 'Formulaire invalide'); + return; + } + const orderLines = this.createPurchaseOrder().lines.value.map(line => ({ + productId: line.productId, + quantity: line.quantity + })); + if (orderLines.length === 0) { + this.notificationService.error('Erreur', 'Aucun produit sélectionné'); + return; + } + const purchaseOrder = { + purchaseConditions: form.get('purchaseConditions')!.value, + products: orderLines + }; + try { + await firstValueFrom( + this.purchaseordersService.createPurchaseOrder(purchaseOrder) + ); + this.notificationService.success('Succès', 'Bon de commande créé'); + } catch (e) { + this.notificationService.error('Erreur', 'Erreur lors de la création du bon de commande.'); + } + + } + + async onModalOk() { + await this.addPurchaseOrder(); + this.createPurchaseOrder().createPurchaseOrderForm.reset(); + this.modalButtonPurchaseOrder().isVisible = false; + await this.productTable().fetchProducts(); + } + + onModalCancel() { + this.modalButtonPurchaseOrder().isVisible = false; } openPurchaseOrderForm() { @@ -111,4 +88,51 @@ export class Stock { ); this.createPurchaseOrder().syncSelectedProducts(selectedProducts); } + + async addQuotation() { + if (this.createQuotation().createQuotationForm.invalid) { + this.notificationService.error('Erreur', 'Formulaire invalide'); + return; + } + const orderLines = this.createQuotation().lines.value.map(line => ({ + productId: line.productId, + quantity: line.quantity + })); + if (orderLines.length === 0) { + this.notificationService.error('Erreur', 'Aucun produit sélectionné'); + return; + } + const quotation = { + message: this.createQuotation().createQuotationForm.get('message')!.value, + purchaseConditions: this.createQuotation().createQuotationForm.get('purchaseConditions')!.value, + products: orderLines + }; + try { + await firstValueFrom( + this.quotationsService.createQuotationEndpoint(quotation) + ); + this.notificationService.success('Succès', 'Devis créé'); + } catch (e) { + this.notificationService.error('Erreur', 'Erreur lors de la création du devis.'); + } + + } + + async onModalQuotationOk() { + await this.addQuotation(); + this.createQuotation().createQuotationForm.reset(); + this.modalButtonQuotation().isVisible = false; + await this.productTable().fetchProducts(); + } + + onModalQuotationCancel() { + this.modalButtonQuotation().isVisible = false; + } + + openQuotationForm() { + const selectedProducts = this.productTable().products().filter(p => + this.productTable().selectedIds.includes(p.id) + ); + this.createQuotation().syncSelectedProducts(selectedProducts); + } } diff --git a/src/app/services/api/.openapi-generator/FILES b/src/app/services/api/.openapi-generator/FILES index 0c304de..c32abf7 100644 --- a/src/app/services/api/.openapi-generator/FILES +++ b/src/app/services/api/.openapi-generator/FILES @@ -23,9 +23,11 @@ model/connect-user-dto.ts model/create-deliverer-dto.ts model/create-delivery-note-dto.ts model/create-price-dto.ts +model/create-product-quotation-dto.ts model/create-purchase-order-dto.ts model/create-purchase-order-product-dto.ts model/create-purchase-product-dto.ts +model/create-quotation-dto.ts model/create-quotation-product-dto.ts model/create-setting-dto.ts model/create-supplier-dto.ts diff --git a/src/app/services/api/api/quotations.service.ts b/src/app/services/api/api/quotations.service.ts index 07ec481..7c925c7 100644 --- a/src/app/services/api/api/quotations.service.ts +++ b/src/app/services/api/api/quotations.service.ts @@ -16,6 +16,8 @@ import { HttpClient, HttpHeaders, HttpParams, import { CustomHttpParameterCodec } from '../encoder'; import { Observable } from 'rxjs'; +// @ts-ignore +import { CreateQuotationDto } from '../model/create-quotation-dto'; // @ts-ignore import { GetQuotationDto } from '../model/get-quotation-dto'; // @ts-ignore @@ -39,6 +41,70 @@ export class QuotationsService extends BaseService { super(basePath, configuration); } + /** + * @endpoint post /API/quotations + * @param createQuotationDto + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public createQuotationEndpoint(createQuotationDto: CreateQuotationDto, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public createQuotationEndpoint(createQuotationDto: CreateQuotationDto, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public createQuotationEndpoint(createQuotationDto: CreateQuotationDto, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public createQuotationEndpoint(createQuotationDto: CreateQuotationDto, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (createQuotationDto === null || createQuotationDto === undefined) { + throw new Error('Required parameter createQuotationDto was null or undefined when calling createQuotationEndpoint.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/API/quotations`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: createQuotationDto, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + /** * @endpoint delete /API/quotations/{id} * @param id diff --git a/src/app/services/api/model/create-product-quotation-dto.ts b/src/app/services/api/model/create-product-quotation-dto.ts new file mode 100644 index 0000000..76033ca --- /dev/null +++ b/src/app/services/api/model/create-product-quotation-dto.ts @@ -0,0 +1,16 @@ +/** + * PyroFetes + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface CreateProductQuotationDto { + productId?: number; + quantity?: number; +} + diff --git a/src/app/services/api/model/create-quotation-dto.ts b/src/app/services/api/model/create-quotation-dto.ts new file mode 100644 index 0000000..7b6faaa --- /dev/null +++ b/src/app/services/api/model/create-quotation-dto.ts @@ -0,0 +1,18 @@ +/** + * PyroFetes + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { CreateProductQuotationDto } from './create-product-quotation-dto'; + + +export interface CreateQuotationDto { + message?: string | null; + conditionsSale?: string | null; + products?: Array | null; +} + diff --git a/src/app/services/api/model/create-quotation-product-dto.ts b/src/app/services/api/model/create-quotation-product-dto.ts index 95a3c60..b6ea6d3 100644 --- a/src/app/services/api/model/create-quotation-product-dto.ts +++ b/src/app/services/api/model/create-quotation-product-dto.ts @@ -19,7 +19,7 @@ export interface CreateQuotationProductDto { productName?: string | null; productDuration?: number; productCaliber?: number; - productApprovalNumber?: number; + productApprovalNumber?: string | null; productWeight?: number; productNec?: number; productImage?: string | null; diff --git a/src/app/services/api/model/get-quotation-dto.ts b/src/app/services/api/model/get-quotation-dto.ts index 2143ee1..a8d25be 100644 --- a/src/app/services/api/model/get-quotation-dto.ts +++ b/src/app/services/api/model/get-quotation-dto.ts @@ -14,6 +14,6 @@ export interface GetQuotationDto { id?: number; message?: string | null; conditionsSale?: string | null; - getQuotationProductDto?: Array | null; + products?: Array | null; } diff --git a/src/app/services/api/model/get-quotation-product-dto.ts b/src/app/services/api/model/get-quotation-product-dto.ts index 09ca5f0..89eb74d 100644 --- a/src/app/services/api/model/get-quotation-product-dto.ts +++ b/src/app/services/api/model/get-quotation-product-dto.ts @@ -19,7 +19,7 @@ export interface GetQuotationProductDto { productName?: string | null; productDuration?: number; productCaliber?: number; - productApprovalNumber?: number; + productApprovalNumber?: string | null; productWeight?: number; productNec?: number; productImage?: string | null; diff --git a/src/app/services/api/model/models.ts b/src/app/services/api/model/models.ts index 56ec3fb..f8ad444 100644 --- a/src/app/services/api/model/models.ts +++ b/src/app/services/api/model/models.ts @@ -2,9 +2,11 @@ export * from './connect-user-dto'; export * from './create-deliverer-dto'; export * from './create-delivery-note-dto'; export * from './create-price-dto'; +export * from './create-product-quotation-dto'; export * from './create-purchase-order-dto'; export * from './create-purchase-order-product-dto'; export * from './create-purchase-product-dto'; +export * from './create-quotation-dto'; export * from './create-quotation-product-dto'; export * from './create-setting-dto'; export * from './create-supplier-dto';