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 @@
+
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;
}
}