Refactor PrestashopService and ps-product-dialog component to enhance API integration and improve product management functionality
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import {Component, Inject, OnInit, inject, OnDestroy} from '@angular/core';
|
// File: src/app/components/ps-product-dialog/ps-product-dialog.component.ts
|
||||||
|
import { Component, Inject, OnInit, inject, OnDestroy } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
||||||
@@ -20,6 +21,7 @@ import { catchError, forkJoin, of, Observable } from 'rxjs';
|
|||||||
import { PsItem } from '../../interfaces/ps-item';
|
import { PsItem } from '../../interfaces/ps-item';
|
||||||
import { ProductListItem } from '../../interfaces/product-list-item';
|
import { ProductListItem } from '../../interfaces/product-list-item';
|
||||||
import { PrestashopService } from '../../services/prestashop.serivce';
|
import { PrestashopService } from '../../services/prestashop.serivce';
|
||||||
|
import { PsProduct } from '../../interfaces/ps-product'; // si tu as une interface dédiée
|
||||||
|
|
||||||
export type ProductDialogData = {
|
export type ProductDialogData = {
|
||||||
mode: 'create' | 'edit';
|
mode: 'create' | 'edit';
|
||||||
@@ -87,6 +89,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
// ---------- Helpers locaux ----------
|
// ---------- Helpers locaux ----------
|
||||||
|
|
||||||
|
// utilisé seulement pour pré-afficher un TTC approximatif à partir du HT de la liste
|
||||||
private toTtc(ht: number) {
|
private toTtc(ht: number) {
|
||||||
return Math.round(((ht * 1.2) + Number.EPSILON) * 100) / 100;
|
return Math.round(((ht * 1.2) + Number.EPSILON) * 100) / 100;
|
||||||
}
|
}
|
||||||
@@ -148,6 +151,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
if (this.mode === 'edit' && this.productRow) {
|
if (this.mode === 'edit' && this.productRow) {
|
||||||
const r = this.productRow;
|
const r = this.productRow;
|
||||||
|
|
||||||
|
// pré-remplissage rapide avec la ligne de liste (HT -> TTC approximatif)
|
||||||
const immediateTtc = r.priceHt == null ? 0 : this.toTtc(r.priceHt);
|
const immediateTtc = r.priceHt == null ? 0 : this.toTtc(r.priceHt);
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
name: r.name,
|
name: r.name,
|
||||||
@@ -158,11 +162,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const details$ = this.ps.getProductDetails(r.id).pipe(
|
const details$ = this.ps.getProductDetails(r.id).pipe(
|
||||||
catchError(() => of({
|
catchError(() => of<PsProduct | null>(null))
|
||||||
id: r.id, name: r.name, description: '',
|
|
||||||
id_manufacturer: r.id_manufacturer, id_supplier: r.id_supplier,
|
|
||||||
id_category_default: r.id_category_default, priceHt: r.priceHt ?? 0
|
|
||||||
}))
|
|
||||||
);
|
);
|
||||||
const qty$ = this.ps.getProductQuantity(r.id).pipe(catchError(() => of(0)));
|
const qty$ = this.ps.getProductQuantity(r.id).pipe(catchError(() => of(0)));
|
||||||
const imgs$ = this.ps.getProductImageUrls(r.id).pipe(catchError(() => of<string[]>([])));
|
const imgs$ = this.ps.getProductImageUrls(r.id).pipe(catchError(() => of<string[]>([])));
|
||||||
@@ -172,20 +172,23 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
forkJoin({ details: details$, qty: qty$, imgs: imgs$, flags: flags$ })
|
forkJoin({ details: details$, qty: qty$, imgs: imgs$, flags: flags$ })
|
||||||
.subscribe(({ details, qty, imgs, flags }) => {
|
.subscribe(({ details, qty, imgs, flags }) => {
|
||||||
const ttc = this.toTtc(details.priceHt ?? 0);
|
const baseDesc = this.cleanForTextarea(details?.description ?? '');
|
||||||
const baseDesc = this.cleanForTextarea(details.description ?? '');
|
|
||||||
this.lastLoadedDescription = baseDesc;
|
this.lastLoadedDescription = baseDesc;
|
||||||
|
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
|
// description depuis le backend (déjà propre / nettoyée normalement)
|
||||||
description: baseDesc,
|
description: baseDesc,
|
||||||
|
// flags
|
||||||
complete: flags.complete,
|
complete: flags.complete,
|
||||||
hasManual: flags.hasManual,
|
hasManual: flags.hasManual,
|
||||||
conditionLabel: flags.conditionLabel || '',
|
conditionLabel: flags.conditionLabel || '',
|
||||||
priceTtc: (ttc || this.form.value.priceTtc || 0),
|
// TTC "réel" calculé par le backend (si dispo) sinon on garde la valeur actuelle
|
||||||
|
priceTtc: (details?.priceTtc ?? this.form.value.priceTtc ?? 0),
|
||||||
quantity: qty,
|
quantity: qty,
|
||||||
categoryId: (details.id_category_default ?? this.form.value.categoryId) ?? null,
|
// ids de ref issus du DTO backend
|
||||||
manufacturerId: (details.id_manufacturer ?? this.form.value.manufacturerId) ?? null,
|
categoryId: (details?.categoryId ?? this.form.value.categoryId) ?? null,
|
||||||
supplierId: (details.id_supplier ?? this.form.value.supplierId) ?? null
|
manufacturerId: (details?.manufacturerId ?? this.form.value.manufacturerId) ?? null,
|
||||||
|
supplierId: (details?.supplierId ?? this.form.value.supplierId) ?? null
|
||||||
});
|
});
|
||||||
|
|
||||||
this.existingImageUrls = imgs;
|
this.existingImageUrls = imgs;
|
||||||
@@ -203,7 +206,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
const fl = (ev.target as HTMLInputElement).files;
|
const fl = (ev.target as HTMLInputElement).files;
|
||||||
|
|
||||||
// Nettoyage des anciens objectURL
|
// Nettoyage des anciens objectURL
|
||||||
for(let url of this.previewUrls) {
|
for (let url of this.previewUrls) {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
this.previewUrls = [];
|
this.previewUrls = [];
|
||||||
@@ -261,7 +264,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- Save / close inchangés (à part dto.images) --------
|
// -------- Save / close --------
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
if (this.form.invalid) return;
|
if (this.form.invalid) return;
|
||||||
@@ -269,7 +272,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
const v = this.form.getRawValue();
|
const v = this.form.getRawValue();
|
||||||
const effectiveDescription = (v.description ?? '').trim() || this.lastLoadedDescription;
|
const effectiveDescription = (v.description ?? '').trim() || this.lastLoadedDescription;
|
||||||
|
|
||||||
const dto = {
|
const dto: PsProduct = {
|
||||||
name: v.name!,
|
name: v.name!,
|
||||||
description: effectiveDescription,
|
description: effectiveDescription,
|
||||||
categoryId: +v.categoryId!,
|
categoryId: +v.categoryId!,
|
||||||
|
|||||||
@@ -1,165 +1,301 @@
|
|||||||
|
// File: src/app/services/prestashop.service.ts
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
import { Observable, map } from 'rxjs';
|
import { Observable, map, of } from 'rxjs';
|
||||||
|
|
||||||
import { PsItem } from '../interfaces/ps-item';
|
import { PsItem } from '../interfaces/ps-item';
|
||||||
import { PsProduct } from '../interfaces/ps-product';
|
import { PsProduct } from '../interfaces/ps-product';
|
||||||
import { ProductListItem } from '../interfaces/product-list-item';
|
import { ProductListItem } from '../interfaces/product-list-item';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
export type Resource = 'categories' | 'manufacturers' | 'suppliers';
|
type Resource = 'categories' | 'manufacturers' | 'suppliers';
|
||||||
|
|
||||||
export interface ProductFlags {
|
|
||||||
complete: boolean;
|
|
||||||
hasManual: boolean;
|
|
||||||
conditionLabel?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class PrestashopService {
|
export class PrestashopService {
|
||||||
private readonly http = inject(HttpClient);
|
private readonly http = inject(HttpClient);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base de l'API Spring.
|
* Base de ton API Spring.
|
||||||
* Exemple dans environment:
|
* Exemple : https://dev.vincent-guillet.fr/gameovergne-api
|
||||||
* apiUrl: '/gameovergne-api/api'
|
*
|
||||||
|
* Assure-toi d'avoir dans environment :
|
||||||
|
* apiUrl: 'https://dev.vincent-guillet.fr/gameovergne-api'
|
||||||
*/
|
*/
|
||||||
private readonly base = `${environment.apiUrl}/ps-admin`;
|
private readonly apiBase = environment.apiUrl;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
/** Endpoints backend "intelligents" (logique métier Presta) */
|
||||||
// RÉFÉRENTIELS SIMPLES : categories / manufacturers / suppliers
|
private readonly adminBase = `${this.apiBase}/api/ps-admin`;
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
list(resource: Resource): Observable<PsItem[]> {
|
/** Endpoints proxy bruts vers Presta (si tu en as encore besoin) */
|
||||||
return this.http.get<PsItem[]>(`${this.base}/${resource}`);
|
private readonly proxyBase = `${this.apiBase}/api/ps`;
|
||||||
}
|
|
||||||
|
|
||||||
create(resource: Resource, name: string): Observable<number> {
|
// ===========================================================================
|
||||||
const params = new HttpParams().set('name', name);
|
// 1) CRUD générique (categories / manufacturers / suppliers)
|
||||||
return this.http.post<number>(`${this.base}/${resource}`, null, { params });
|
// ===========================================================================
|
||||||
}
|
|
||||||
|
|
||||||
update(resource: Resource, id: number, newName: string): Observable<void> {
|
|
||||||
const params = new HttpParams().set('name', newName);
|
|
||||||
return this.http.put<void>(`${this.base}/${resource}/${id}`, null, { params });
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(resource: Resource, id: number): Observable<void> {
|
|
||||||
return this.http.delete<void>(`${this.base}/${resource}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// PRODUITS : liste, flags, création / mise à jour / suppression
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Liste des produits avec quantité, basée sur l’API Spring.
|
* Liste les catégories / fabricants / fournisseurs.
|
||||||
*
|
* Signature conservée pour les composants existants.
|
||||||
* Le backend renvoie un DTO en camelCase :
|
|
||||||
* {
|
|
||||||
* id: number;
|
|
||||||
* name: string;
|
|
||||||
* manufacturerId?: number;
|
|
||||||
* supplierId?: number;
|
|
||||||
* categoryId?: number;
|
|
||||||
* priceHt?: number;
|
|
||||||
* quantity?: number;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* On le remappe sur ton interface existante ProductListItem
|
|
||||||
* (id_manufacturer, id_supplier, id_category_default, etc.).
|
|
||||||
*/
|
*/
|
||||||
listProducts(query?: string): Observable<ProductListItem[]> {
|
list(resource: Resource): Observable<PsItem[]> {
|
||||||
|
return this.http
|
||||||
|
.get<PsItem[]>(`${this.adminBase}/reference-data/${resource}`)
|
||||||
|
.pipe(
|
||||||
|
// Par sécurité, on trie par nom ici (comme avant)
|
||||||
|
map(items =>
|
||||||
|
[...items].sort((a, b) =>
|
||||||
|
a.name.localeCompare(b.name, 'fr', { sensitivity: 'base' })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une catégorie / manufacturer / supplier.
|
||||||
|
* Retourne l'id créé (ou null si souci).
|
||||||
|
*/
|
||||||
|
create(resource: Resource, name: string): Observable<number | null> {
|
||||||
|
return this.http
|
||||||
|
.post<{ id: number }>(
|
||||||
|
`${this.adminBase}/reference-data/${resource}`,
|
||||||
|
{ name }
|
||||||
|
)
|
||||||
|
.pipe(map(res => (res && typeof res.id === 'number' ? res.id : null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le nom d'une catégorie / manufacturer / supplier.
|
||||||
|
*/
|
||||||
|
update(resource: Resource, id: number, newName: string): Observable<boolean> {
|
||||||
|
return this.http
|
||||||
|
.put<void>(
|
||||||
|
`${this.adminBase}/reference-data/${resource}/${id}`,
|
||||||
|
{ name: newName }
|
||||||
|
)
|
||||||
|
.pipe(map(() => true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime une catégorie / manufacturer / supplier.
|
||||||
|
*/
|
||||||
|
delete(resource: Resource, id: number): Observable<boolean> {
|
||||||
|
return this.http
|
||||||
|
.delete<void>(`${this.adminBase}/reference-data/${resource}/${id}`)
|
||||||
|
.pipe(map(() => true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère le XML brut de l'objet Presta (pour debug / vérif).
|
||||||
|
* Signature conservée, mais on passe désormais par le backend.
|
||||||
|
*/
|
||||||
|
getXml(resource: Resource, id: number): Observable<string> {
|
||||||
|
return this.http.get(
|
||||||
|
`${this.adminBase}/reference-data/${resource}/${id}/raw-xml`,
|
||||||
|
{
|
||||||
|
responseType: 'text',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 2) Produits (liste / détails)
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste les produits pour l'écran de recherche / listing.
|
||||||
|
* Anciennement : multi-requêtes directes sur Presta.
|
||||||
|
* Maintenant : le backend te renvoie déjà les infos nécessaires.
|
||||||
|
*/
|
||||||
|
listProducts(
|
||||||
|
query?: string
|
||||||
|
): Observable<(ProductListItem & { priceHt?: number; quantity?: number })[]> {
|
||||||
let params = new HttpParams();
|
let params = new HttpParams();
|
||||||
if (query?.trim()) {
|
if (query?.trim()) {
|
||||||
params = params.set('q', query.trim());
|
params = params.set('q', query.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.http
|
return this.http.get<
|
||||||
.get<
|
(ProductListItem & { priceHt?: number; quantity?: number })[]
|
||||||
Array<{
|
>(`${this.adminBase}/products`, { params });
|
||||||
id: number;
|
}
|
||||||
name: string;
|
|
||||||
manufacturerId?: number;
|
/**
|
||||||
supplierId?: number;
|
* Détails complets d'un produit.
|
||||||
categoryId?: number;
|
* Le backend renvoie un PsProduct (ou équivalent).
|
||||||
priceHt?: number;
|
*/
|
||||||
quantity?: number;
|
getProductDetails(id: number): Observable<PsProduct> {
|
||||||
}>
|
return this.http.get<PsProduct>(`${this.adminBase}/products/${id}`);
|
||||||
>(`${this.base}/products`, { params })
|
}
|
||||||
.pipe(
|
|
||||||
map((dtoList) =>
|
// ===========================================================================
|
||||||
dtoList.map(
|
// 3) Images produits
|
||||||
(dto) =>
|
// ===========================================================================
|
||||||
({
|
|
||||||
id: dto.id,
|
getProductImageUrls(productId: number) {
|
||||||
name: dto.name,
|
return this.http.get<string[]>(
|
||||||
id_manufacturer: dto.manufacturerId,
|
`${this.adminBase}/products/${productId}/images`
|
||||||
id_supplier: dto.supplierId,
|
|
||||||
id_category_default: dto.categoryId,
|
|
||||||
priceHt: dto.priceHt,
|
|
||||||
quantity: dto.quantity ?? 0,
|
|
||||||
} as ProductListItem),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags Complet / Notice / État pour un produit.
|
* URL de la miniature d'un produit (ou null).
|
||||||
* Le backend renvoie déjà ce format :
|
* On délègue au backend (qui sait comment construire l'URL FO).
|
||||||
* { complete: boolean; hasManual: boolean; conditionLabel?: string }
|
*
|
||||||
|
* Ancienne signature conservée : Observable<string | null>.
|
||||||
*/
|
*/
|
||||||
getProductFlags(productId: number): Observable<ProductFlags> {
|
getProductThumbnailUrl(productId: number): Observable<string | null> {
|
||||||
return this.http.get<ProductFlags>(`${this.base}/products/${productId}/flags`);
|
return this.http
|
||||||
|
.get<{ url: string | null }>(
|
||||||
|
`${this.adminBase}/products/${productId}/thumbnail`
|
||||||
|
)
|
||||||
|
.pipe(map(res => res?.url ?? null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valeurs possibles de la caractéristique "État".
|
* Si tu as encore un usage direct de l'upload dans certains composants,
|
||||||
* Renvoie une simple liste de libellés.
|
* tu peux garder cette méthode mais la faire pointer sur ton endpoint Spring
|
||||||
|
* "upload image only".
|
||||||
|
*
|
||||||
|
* Si tu n'en as pas besoin, tu peux la laisser mais non utilisée.
|
||||||
|
*/
|
||||||
|
uploadProductImage(
|
||||||
|
productId: number,
|
||||||
|
file: File
|
||||||
|
): Observable<unknown> {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append('image', file);
|
||||||
|
|
||||||
|
return this.http.post(
|
||||||
|
`${this.adminBase}/products/${productId}/images`,
|
||||||
|
fd
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 4) Stock (quantité)
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère la quantité du produit dans Presta.
|
||||||
|
*/
|
||||||
|
getProductQuantity(productId: number): Observable<number> {
|
||||||
|
return this.http
|
||||||
|
.get<{ quantity: number }>(
|
||||||
|
`${this.adminBase}/products/${productId}/quantity`
|
||||||
|
)
|
||||||
|
.pipe(map(res => (res?.quantity ?? 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 5) Caractéristiques Complet / Notice / Etat
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags Complet / Notice / Etat d'un produit.
|
||||||
|
* Signature conservée.
|
||||||
|
*/
|
||||||
|
getProductFlags(
|
||||||
|
productId: number
|
||||||
|
): Observable<{ complete: boolean; hasManual: boolean; conditionLabel?: string }> {
|
||||||
|
return this.http.get<{
|
||||||
|
complete: boolean;
|
||||||
|
hasManual: boolean;
|
||||||
|
conditionLabel?: string;
|
||||||
|
}>(`${this.adminBase}/products/${productId}/flags`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste des valeurs possibles pour la caractéristique "État".
|
||||||
|
* Signature conservée (Observable<string[]>).
|
||||||
*/
|
*/
|
||||||
getConditionValues(): Observable<string[]> {
|
getConditionValues(): Observable<string[]> {
|
||||||
return this.http.get<string[]>(`${this.base}/meta/condition-values`);
|
return this.http.get<string[]>(
|
||||||
|
`${this.adminBase}/products/conditions`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 6) Création / mise à jour / suppression d'un produit
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Création produit : on envoie ton PsProduct tel quel.
|
* Construit un FormData pour envoyer :
|
||||||
* Toute la logique XML / TVA / caractéristiques / stock est dans Spring.
|
* - le DTO produit sans les images en JSON ("meta"),
|
||||||
|
* - les fichiers images ("images").
|
||||||
*
|
*
|
||||||
* NB: Si ton interface PsProduct contient `images?: File[]`,
|
* C'est aligné avec un endpoint Spring @PostMapping(consumes = MULTIPART_FORM_DATA)
|
||||||
* Jackson côté Spring ignorera cette propriété (unknown property),
|
* qui prend :
|
||||||
* et tu pourras gérer l'upload dans un endpoint dédié plus tard.
|
* @RequestPart("meta") PsProductDto
|
||||||
|
* @RequestPart(name = "images", required = false) List<MultipartFile>
|
||||||
*/
|
*/
|
||||||
createProduct(dto: PsProduct): Observable<number> {
|
private buildProductFormData(dto: PsProduct): FormData {
|
||||||
return this.http.post<number>(`${this.base}/products`, dto);
|
const fd = new FormData();
|
||||||
|
|
||||||
|
// On clone le DTO sans les images pour l'envoyer en JSON
|
||||||
|
const { images, ...meta } = dto as PsProduct & { images?: File[] };
|
||||||
|
|
||||||
|
fd.append(
|
||||||
|
'meta',
|
||||||
|
new Blob([JSON.stringify(meta)], { type: 'application/json' })
|
||||||
|
);
|
||||||
|
|
||||||
|
if (images && images.length) {
|
||||||
|
for (const img of images) {
|
||||||
|
fd.append('images', img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mise à jour produit.
|
* Création d'un produit Presta via ton backend.
|
||||||
|
* Signature conservée : Observable<number | null> (id du produit Presta).
|
||||||
*/
|
*/
|
||||||
updateProduct(id: number, dto: PsProduct): Observable<void> {
|
createProduct(dto: PsProduct): Observable<number | null> {
|
||||||
return this.http.put<void>(`${this.base}/products/${id}`, dto);
|
const fd = this.buildProductFormData(dto);
|
||||||
|
|
||||||
|
return this.http
|
||||||
|
.post<{ id: number }>(`${this.adminBase}/products`, fd)
|
||||||
|
.pipe(map(res => (res && typeof res.id === 'number' ? res.id : null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suppression produit.
|
* Mise à jour d'un produit Presta via ton backend.
|
||||||
|
* Signature conservée : Observable<boolean>.
|
||||||
*/
|
*/
|
||||||
deleteProduct(id: number): Observable<void> {
|
updateProduct(id: number, dto: PsProduct): Observable<boolean> {
|
||||||
return this.http.delete<void>(`${this.base}/products/${id}`);
|
const fd = this.buildProductFormData(dto);
|
||||||
|
|
||||||
|
return this.http
|
||||||
|
.put<void>(`${this.adminBase}/products/${id}`, fd)
|
||||||
|
.pipe(map(() => true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
/**
|
||||||
// TODO ultérieur : gestion des images produits via Spring
|
* Suppression d'un produit Presta via ton backend.
|
||||||
//
|
* Signature conservée : Observable<boolean>.
|
||||||
// Exemple de signature à garder côté front (sans impl pour l’instant) :
|
*/
|
||||||
//
|
deleteProduct(id: number): Observable<boolean> {
|
||||||
// uploadProductImage(productId: number, file: File): Observable<void> {
|
return this.http
|
||||||
// const formData = new FormData();
|
.delete<void>(`${this.adminBase}/products/${id}`)
|
||||||
// formData.append('image', file);
|
.pipe(map(() => true));
|
||||||
// return this.http.post<void>(`${this.base}/products/${productId}/images`, formData);
|
}
|
||||||
// }
|
|
||||||
//
|
// ===========================================================================
|
||||||
// Il faudra ajouter le contrôleur + service côté Spring pour
|
// 7) (Optionnel) Accès direct proxy /api/ps/** si tu en as encore besoin
|
||||||
// relayer vers /images/products/{id} de Presta.
|
// ===========================================================================
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
/**
|
||||||
|
* Si certains composants utilisent encore directement l'ancien endpoint
|
||||||
|
* "base Presta proxy" (ex: this.base + '/categories?display=...'),
|
||||||
|
* tu peux garder ce helper pour les migrations progressives.
|
||||||
|
*/
|
||||||
|
rawGet(path: string, params?: HttpParams): Observable<string> {
|
||||||
|
const url = `${this.proxyBase}/${path.replace(/^\/+/, '')}`;
|
||||||
|
return this.http.get(url, {
|
||||||
|
params,
|
||||||
|
responseType: 'text',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user