From 961551e9267936282990f41b00ae04b458b20aea Mon Sep 17 00:00:00 2001 From: sanchezvem Date: Tue, 26 May 2026 18:35:44 +0100 Subject: [PATCH] Added checkbox in stock page --- .../add-product-supplier-form.css | 0 .../add-product-supplier-form.html | 40 ++++ .../add-product-supplier-form.ts | 68 +++++++ .../components/stock-table/stock-table.html | 19 +- src/app/components/stock-table/stock-table.ts | 191 +++++++----------- .../components/supplier-form/supplier-form.ts | 4 +- src/app/pages/stock/stock.html | 21 +- src/app/pages/stock/stock.ts | 156 +++++++++----- 8 files changed, 310 insertions(+), 189 deletions(-) create mode 100644 src/app/components/add-product-supplier-form/add-product-supplier-form.css create mode 100644 src/app/components/add-product-supplier-form/add-product-supplier-form.html create mode 100644 src/app/components/add-product-supplier-form/add-product-supplier-form.ts diff --git a/src/app/components/add-product-supplier-form/add-product-supplier-form.css b/src/app/components/add-product-supplier-form/add-product-supplier-form.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/add-product-supplier-form/add-product-supplier-form.html b/src/app/components/add-product-supplier-form/add-product-supplier-form.html new file mode 100644 index 0000000..66a94c5 --- /dev/null +++ b/src/app/components/add-product-supplier-form/add-product-supplier-form.html @@ -0,0 +1,40 @@ +
+ + + Fournisseur + + + + + @for (supplier of suppliers(); track supplier.id){ + + } + + + + +
+ + + + Produit + Prix + + + + @for (line of lines.controls; let i = $index; track i) { + + {{ line.value.name }} + + + + + + } + + +
+
diff --git a/src/app/components/add-product-supplier-form/add-product-supplier-form.ts b/src/app/components/add-product-supplier-form/add-product-supplier-form.ts new file mode 100644 index 0000000..9060015 --- /dev/null +++ b/src/app/components/add-product-supplier-form/add-product-supplier-form.ts @@ -0,0 +1,68 @@ +import {Component, inject, OnInit, signal} from '@angular/core'; +import {FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; +import {GetProductDto, GetSupplierDto, SuppliersService} from "../../services/api"; +import {NzColDirective, NzRowDirective} from "ng-zorro-antd/grid"; +import {NzFormControlComponent, NzFormLabelComponent} from "ng-zorro-antd/form"; +import {NzFlexDirective} from "ng-zorro-antd/flex"; +import {NzTableComponent} from "ng-zorro-antd/table"; +import {NzInputNumberComponent} from "ng-zorro-antd/input-number"; +import {NzOptionComponent, NzSelectComponent} from "ng-zorro-antd/select"; +import {firstValueFrom} from "rxjs"; +import {NzNotificationService} from "ng-zorro-antd/notification"; + +@Component({ + selector: 'app-add-product-supplier-form', + imports: [ + NzRowDirective, + NzFormControlComponent, + NzFormLabelComponent, + ReactiveFormsModule, + NzFlexDirective, + NzColDirective, + NzTableComponent, + NzInputNumberComponent, + NzOptionComponent, + NzSelectComponent + ], + templateUrl: './add-product-supplier-form.html', + styleUrl: './add-product-supplier-form.css', +}) +export class AddProductSupplierForm implements OnInit { + addProductForm: FormGroup = new FormGroup({ + supplierId: new FormControl(null, Validators.required), + lines: new FormArray([], Validators.required), + }); + + private suppliersServices = inject(SuppliersService); + private notificationService = inject(NzNotificationService); + + suppliers = signal([]); + + async ngOnInit() { + try { + const suppliers = await firstValueFrom(this.suppliersServices.getAllSuppliersEndpoint()); + this.suppliers.set(suppliers); + } + catch { + this.notificationService.error('Erreur', 'Erreur de communication avec l\'API'); + } + } + + get lines(): FormArray { + return this.addProductForm.get('lines') as FormArray; + } + + addProductToForm(selectedProducts: GetProductDto[]) { + this.lines.clear(); + + selectedProducts.forEach(p => { + this.lines.push( + new FormGroup({ + productId: new FormControl(p.id), + name: new FormControl(p.name), + price: new FormControl(1, [Validators.required,Validators.min(0)]) + }) + ); + }); + } +} diff --git a/src/app/components/stock-table/stock-table.html b/src/app/components/stock-table/stock-table.html index 10db515..55148da 100644 --- a/src/app/components/stock-table/stock-table.html +++ b/src/app/components/stock-table/stock-table.html @@ -1,18 +1,16 @@ - + Nom Référence @@ -27,16 +25,15 @@ - @for (product of filteredProducts(); track product.id) { + @for (product of products(); track product.id) { - {{ product.name }} {{ product.references }} {{ product.nec }} diff --git a/src/app/components/stock-table/stock-table.ts b/src/app/components/stock-table/stock-table.ts index e0857d2..d2000a0 100644 --- a/src/app/components/stock-table/stock-table.ts +++ b/src/app/components/stock-table/stock-table.ts @@ -1,14 +1,14 @@ -import {Component, computed, inject, OnInit, output, signal, viewChild} from '@angular/core'; +import {Component, inject, OnInit, output, signal, viewChild} from '@angular/core'; import {NzTableComponent, NzThMeasureDirective} from "ng-zorro-antd/table"; import {ModalNav} from "../modal-nav/modal-nav"; import {NzIconDirective} from "ng-zorro-antd/icon"; import {StockForm} from "../stock-form/stock-form"; import {NzDividerComponent} from "ng-zorro-antd/divider"; import {FormsModule} from "@angular/forms"; -import {NzCheckboxComponent} from "ng-zorro-antd/checkbox"; import {GetProductDto, ProductsService, WarehouseproductsService} from "../../services/api"; import {NzNotificationService} from "ng-zorro-antd/notification"; -import {firstValueFrom} from "rxjs"; +import {first, firstValueFrom} from "rxjs"; +import {NzCheckboxComponent} from "ng-zorro-antd/checkbox"; interface ProductWithQuantity extends GetProductDto { totalQuantity?: number; @@ -23,8 +23,8 @@ interface ProductWithQuantity extends GetProductDto { StockForm, NzDividerComponent, FormsModule, - NzCheckboxComponent, NzThMeasureDirective, + NzCheckboxComponent, ], templateUrl: './stock-table.html', styleUrl: './stock-table.css', @@ -34,109 +34,49 @@ export class StockTable implements OnInit { private productsService = inject(ProductsService); private wareHousseProductsService = inject(WarehouseproductsService) private notificationService = inject(NzNotificationService) + products = signal([]); productsLoading = signal(false); - updateProduct = viewChild.required('stockForm'); + modal = viewChild.required('modalNav'); - checked = false; - indeterminate = false; - setOfCheckedId = new Set(); - selectionChange = output() - currentPageData: GetProductDto[] = []; + selectionChange = output(); + productsTables = output(); - private searchQuery = signal(''); - - filteredProducts = computed(() => { - const q = this.searchQuery().toLowerCase().trim(); - - if (!q) return this.products(); - - return this.products().filter(s => { - const name = (s.name ?? '').toLowerCase(); - return name.includes(q); - }); - }); - - applySearch(query: string) { - this.searchQuery.set(query); - } - - get hasSelection(): boolean { - return this.setOfCheckedId.size > 0; - } - - updateCheckedSet(id: number, checked: boolean): void { - if (checked) this.setOfCheckedId.add(id); - else this.setOfCheckedId.delete(id); - } - - onItemChecked(id: number, checked: boolean): void { - this.updateCheckedSet(id, checked); - this.refreshCheckedStatus(); - } - - onAllChecked(checked: boolean): void { - this.currentPageData.forEach(item => - this.updateCheckedSet(item.id, checked) - ); - this.refreshCheckedStatus(); - } - - onCurrentPageDataChange($event: GetProductDto[]): void { - this.currentPageData = $event; - this.refreshCheckedStatus(); - } - - refreshCheckedStatus(): void { - const total = this.currentPageData.length; - const checkedCount = this.currentPageData.filter(p => this.setOfCheckedId.has(p.id)).length; - - this.checked = checkedCount === total; - this.indeterminate = checkedCount > 0 && checkedCount < total; - - setTimeout(() => this.selectionChange.emit(this.hasSelection)); - } - - get selectedIds() { - return Array.from(this.setOfCheckedId); - } + selectedProduct: GetProductDto | null = null; + checked: boolean = false; + indeterminate: boolean = false; + selectedIds: number[] = []; async ngOnInit() { await this.fetchProducts(); } - async fetchTotalQuantity(product: ProductWithQuantity) { - try { - const res = await firstValueFrom( - this.wareHousseProductsService.getTotalQuantityEndpoint(product.id) - ); - product.totalQuantity = res.totalQuantity; - } catch (e) { - product.totalQuantity = 0; - } - } - async fetchProducts() { this.productsLoading.set(true); try { const products = await firstValueFrom(this.productsService.getAllProductsEndpoint()); - // transforme chaque produit en ProductWithQuantity - const productsWithQuantity: ProductWithQuantity[] = products.map(p => ({...p})); - - this.products.set(productsWithQuantity); - - // récupérer la quantité pour chaque produit en parallèle - await Promise.all(productsWithQuantity.map(p => this.fetchTotalQuantity(p))); - - // déclencher la mise à jour du signal - this.products.set([...productsWithQuantity]); - } catch (e) { - this.notificationService.error( - 'Erreur', - 'Erreur de communication avec l\'API' + const productsWithQuantity = await Promise.all( + products.map(async (x) => { + try { + const quantity = await firstValueFrom(this.wareHousseProductsService.getTotalQuantityEndpoint(x.id)); + return { + ...x, + totalQuantity: quantity.totalQuantity ?? 0 + }; + } catch { + return { + ...x, + totalQuantity: 0 + }; + } + }) ); + this.products.set(productsWithQuantity); + this.productsTables.emit(productsWithQuantity); + } catch { + this.notificationService.error('Erreur', 'Erreur de communication avec l\'API'); } this.productsLoading.set(false); } @@ -144,48 +84,28 @@ export class StockTable implements OnInit { async delete(productId: number) { try { await firstValueFrom(this.productsService.deleteProductEndpoint(productId)) - this.notificationService.success( - 'Success', - 'Suppression effectuée' - ) - } catch (e) { - this.notificationService.error( - 'Erreur', - 'Impossible de supprimer la ligne' - ) + this.notificationService.success('Success', 'Suppression effectuée'); + } catch { + this.notificationService.error('Erreur', 'Impossible de supprimer la ligne'); } await this.fetchProducts(); } async edit(id: number, updateProductComponent: StockForm) { if (updateProductComponent.stockForm.invalid) { - this.notificationService.error( - 'Erreur', - 'Erreur d\'écriture dans le formulaire' - ) + this.notificationService.error('Erreur', 'Erreur d\'écriture dans le formulaire') return; } - try { - const products = updateProductComponent.stockForm.getRawValue(); - await firstValueFrom(this.productsService.patchProductMinimalStockEndpoint(id, products)) - - this.notificationService.success( - 'Success', - 'Limite de stock modifiée' - ) - } catch (e) { - console.error(e); - this.notificationService.error( - 'Erreur', - 'Erreur lors de la modification' - ) + await firstValueFrom(this.productsService.patchProductMinimalStockEndpoint(id, products)); + await this.fetchProducts(); + this.notificationService.success('Success', 'Limite de stock modifiée'); + } catch { + this.notificationService.error('Erreur', 'Erreur lors de la modification'); } } - selectedProduct: GetProductDto | null = null; - openEditModal(product: GetProductDto) { this.selectedProduct = {...product}; this.modal().showModal(); @@ -193,7 +113,6 @@ export class StockTable implements OnInit { async onModalOk(productId: number, updateProductComponent: StockForm, modal: ModalNav) { if (!this.selectedProduct) return; - await this.edit(productId, updateProductComponent); updateProductComponent.stockForm.reset(); modal.isVisible = false; @@ -203,4 +122,34 @@ export class StockTable implements OnInit { onModalCancel(modal: ModalNav) { modal.isVisible = false; } + + updateCheck(id: number, checked: boolean) { + if (checked) { + if (!this.selectedIds.includes(id)) { + this.selectedIds.push(id); + } + } else this.selectedIds = this.selectedIds.filter(x => x !== id); + } + + refreshCheckStatus() { + const total = this.products().length; + const checkedCount = this.selectedIds.length; + + this.checked = checkedCount === total; + this.indeterminate = checkedCount > 0 && checkedCount < total; + + this.selectionChange.emit(this.selectedIds); + } + + allCheck(checked: boolean) { + this.products().forEach(x => + this.updateCheck(x.id, checked) + ); + this.refreshCheckStatus(); + } + + onItemChecked(id: number, checked: boolean): void { + this.updateCheck(id, checked); + this.refreshCheckStatus(); + } } \ No newline at end of file diff --git a/src/app/components/supplier-form/supplier-form.ts b/src/app/components/supplier-form/supplier-form.ts index 2686f33..d3581a8 100644 --- a/src/app/components/supplier-form/supplier-form.ts +++ b/src/app/components/supplier-form/supplier-form.ts @@ -22,6 +22,8 @@ import {GetSupplierDto} from "../../services/api"; styleUrl: './supplier-form.css', }) export class SupplierForm { + supplier = input(); + supplierForm: FormGroup = new FormGroup({ name: new FormControl(null, [Validators.required]), email: new FormControl(null, [Validators.required]), @@ -33,8 +35,6 @@ export class SupplierForm { }) - supplier = input(); - constructor() { effect(() => { if (this.supplier()) { diff --git a/src/app/pages/stock/stock.html b/src/app/pages/stock/stock.html index be430c1..f1f29bd 100644 --- a/src/app/pages/stock/stock.html +++ b/src/app/pages/stock/stock.html @@ -1,9 +1,9 @@
- @if (hasSelection) { + @if (productIds.length) { + + + + }
- + +
\ No newline at end of file diff --git a/src/app/pages/stock/stock.ts b/src/app/pages/stock/stock.ts index e30b475..a12156e 100644 --- a/src/app/pages/stock/stock.ts +++ b/src/app/pages/stock/stock.ts @@ -1,11 +1,12 @@ -import {Component, inject, viewChild} from '@angular/core'; +import {Component, inject, signal, viewChild} from '@angular/core'; import {StockTable} from "../../components/stock-table/stock-table"; import {ModalButton} from "../../components/modal-button/modal-button"; -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 {NzNotificationService} from "ng-zorro-antd/notification"; +import {CreatePriceDto, PurchaseordersService, QuotationsService, SuppliersService} from "../../services/api"; +import {firstValueFrom} from "rxjs"; import {CreateQuotationForm} from "../../components/create-quotation-form/create-quotation-form"; +import {AddProductSupplierForm} from "../../components/add-product-supplier-form/add-product-supplier-form"; @Component({ selector: 'app-stock', @@ -14,6 +15,7 @@ import {CreateQuotationForm} from "../../components/create-quotation-form/create ModalButton, CreatePurchaseorderForm, CreateQuotationForm, + AddProductSupplierForm, ], templateUrl: './stock.html', styleUrl: './stock.css', @@ -22,112 +24,164 @@ import {CreateQuotationForm} from "../../components/create-quotation-form/create export class Stock { createPurchaseOrder = viewChild.required('purchaseOrderForm'); createQuotation = viewChild.required('quotationForm'); - productTable = viewChild.required('stockTable'); - private purchaseordersService = inject(PurchaseordersService) - private quotationsService = inject(QuotationsService) - private notificationService = inject(NzNotificationService) + addProduct = viewChild.required('supplierForm'); modalButtonPurchaseOrder = viewChild.required('modalButtonPurchaseOrder'); modalButtonQuotation = viewChild.required('modalButtonQuotation'); + modalButtonSupplier = viewChild.required('modalButtonSupplier'); - hasSelection = false; + private purchaseOrdersService = inject(PurchaseordersService); + private quotationsService = inject(QuotationsService); + private suppliersService = inject(SuppliersService); + private notificationService = inject(NzNotificationService); - onSelectionChange(value: boolean) { - this.hasSelection = value; + products = signal([]); + + productIds = []; + + getSelectedProducts() { + return this.products().filter(x => this.productIds.includes(x.id)); + } + + openPurchaseOrderForm() { + this.createPurchaseOrder().syncSelectedProducts(this.getSelectedProducts()); + } + + openQuotationForm() { + this.createQuotation().syncSelectedProducts(this.getSelectedProducts()); + } + + openSupplierForm() { + this.addProduct().addProductToForm(this.getSelectedProducts()); } 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) { + + if (!orderLines.length) { 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) - ); + await firstValueFrom(this.purchaseOrdersService.createPurchaseOrder(purchaseOrder)); this.notificationService.success('Succès', 'Bon de commande créé'); - } catch (e) { + } catch { 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() { - const selectedProducts = this.productTable().products().filter(p => - this.productTable().selectedIds.includes(p.id) - ); - this.createPurchaseOrder().syncSelectedProducts(selectedProducts); } async addQuotation() { - if (this.createQuotation().createQuotationForm.invalid) { + const form = this.createQuotation().createQuotationForm; + + if (form.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) { + + if (!orderLines.length) { 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) { - console.log(this.createQuotation()); - this.notificationService.error('Erreur', 'Erreur lors de la création du devis.'); + await firstValueFrom(this.quotationsService.createQuotationEndpoint(quotation)); + this.notificationService.success('Succès', 'Bon de commande créé'); + } catch { + this.notificationService.error('Erreur', 'Erreur lors de la création du bon de commande.'); + } + } + + async addProductFromSupplier() { + const form = this.addProduct().addProductForm; + let success = 0; + + if (form.invalid) { + this.notificationService.error('Erreur', 'Formulaire invalide'); + return; } + const supplierId = form.value.supplierId; + const lines = this.addProduct().lines.value; + + if (!lines.length) { + this.notificationService.error('Erreur', 'Aucun produit sélectionné'); + return; + } + + for (const line of lines) { + try { + await firstValueFrom( + this.suppliersService.addProductToSupplierEndpoint( + supplierId, + line.productId, + { + sellingPrice: line.price + } + ) + ); + + success++; + } catch {} + } + this.notificationService.success('Succès', `${success} produits ajoutés`); + } + + async onModalPurchaseOrderOk() { + await this.addPurchaseOrder(); + this.createPurchaseOrder().createPurchaseOrderForm.reset(); + this.modalButtonPurchaseOrder().isVisible = false; + this.onModalPurchaseOrderCancel(); + } + + onModalPurchaseOrderCancel() { + this.modalButtonPurchaseOrder().isVisible = false; } async onModalQuotationOk() { - console.log(this.createQuotation().createQuotationForm.getRawValue()); await this.addQuotation(); this.createQuotation().createQuotationForm.reset(); this.modalButtonQuotation().isVisible = false; - await this.productTable().fetchProducts(); + this.onModalQuotationCancel(); } onModalQuotationCancel() { this.modalButtonQuotation().isVisible = false; } - openQuotationForm() { - const selectedProducts = this.productTable().products().filter(p => - this.productTable().selectedIds.includes(p.id) - ); - this.createQuotation().syncSelectedProducts(selectedProducts); + async onModalSupplierOk() { + await this.addProductFromSupplier(); + this.addProduct().addProductForm.reset(); + this.modalButtonSupplier().isVisible = false; + this.onModalSupplierCancel(); + } + + onModalSupplierCancel() { + this.modalButtonSupplier().isVisible = false; } }