511 lines
17 KiB
Markdown
511 lines
17 KiB
Markdown
|
|
# Plan d'implémentation v0.702 — Reviews, Factures, Remboursements & Product Detail
|
||
|
|
|
||
|
|
## État des lieux
|
||
|
|
|
||
|
|
Les fonctionnalités reviews (R1), factures (F1) et remboursements (R2) sont **déjà implémentées** :
|
||
|
|
|
||
|
|
| Feature | Backend | Frontend service | Frontend UI | Route | MSW | Tests backend |
|
||
|
|
|---------|---------|-----------------|-------------|-------|-----|---------------|
|
||
|
|
| Reviews | ✅ service, handler, route | ✅ createReview, listReviews | ✅ ProductDetailViewReviews, ReviewProductModal | ❌ `/marketplace/products/:id` manque | ❌ | ❌ |
|
||
|
|
| Invoices | ✅ invoice.go, handler, route | ✅ downloadInvoice | ✅ PurchasesViewItem, LicensesView | ✅ (via purchases) | ❌ | ❌ |
|
||
|
|
| Refunds | ✅ RefundOrder, handler, route | ✅ refundOrder | ✅ RefundRequestModal | ✅ (via purchases) | ✅ | ❌ |
|
||
|
|
|
||
|
|
**Ce plan complète le câblage, les tests et la documentation.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Fichiers existants clés
|
||
|
|
|
||
|
|
- Models : [`models.go`](veza-backend-api/internal/core/marketplace/models.go) (ProductReview ligne 212, Order ligne 170, License ligne 119)
|
||
|
|
- Service : [`service.go`](veza-backend-api/internal/core/marketplace/service.go) (CreateReview L1026, ListReviews L1071, RefundOrder L1098)
|
||
|
|
- Invoice : [`invoice.go`](veza-backend-api/internal/core/marketplace/invoice.go) (GenerateInvoice L17, buildInvoicePDF L44)
|
||
|
|
- Handlers : [`marketplace.go`](veza-backend-api/internal/handlers/marketplace.go) (CreateReview L314, ListReviews L350, GetOrderInvoice L791, RefundOrder L751)
|
||
|
|
- Routes : [`routes_marketplace.go`](veza-backend-api/internal/api/routes_marketplace.go)
|
||
|
|
- Frontend service : [`marketplaceService.ts`](apps/web/src/services/marketplaceService.ts) (createReview L235, listReviews L248, downloadInvoice L166, refundOrder L227)
|
||
|
|
- ProductDetailView : [`ProductDetailView.tsx`](apps/web/src/components/marketplace/product-detail-view/ProductDetailView.tsx)
|
||
|
|
- Reviews UI : [`ProductDetailViewReviews.tsx`](apps/web/src/components/marketplace/product-detail-view/ProductDetailViewReviews.tsx)
|
||
|
|
- ReviewModal : [`ReviewProductModal.tsx`](apps/web/src/components/marketplace/modals/ReviewProductModal.tsx)
|
||
|
|
- PurchasesView : [`PurchasesView.tsx`](apps/web/src/features/purchases/pages/purchases-page/PurchasesView.tsx)
|
||
|
|
- MSW marketplace : [`handlers-marketplace.ts`](apps/web/src/mocks/handlers-marketplace.ts) (353 lignes)
|
||
|
|
- Tests webhook : [`process_webhook_test.go`](veza-backend-api/internal/core/marketplace/process_webhook_test.go)
|
||
|
|
- Stories existantes : [`ProductDetailView.stories.tsx`](apps/web/src/components/marketplace/ProductDetailView.stories.tsx)
|
||
|
|
- Lazy exports : [`lazyExports.ts`](apps/web/src/components/ui/lazy-component/lazyExports.ts)
|
||
|
|
- Routes : [`routeConfig.tsx`](apps/web/src/router/routeConfig.tsx)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sprint 1 : Lot W1 — Product Detail Page route
|
||
|
|
|
||
|
|
### Step 1 : ProductDetailPage + lazy export + route
|
||
|
|
|
||
|
|
**Fichier** : `apps/web/src/features/marketplace/pages/ProductDetailPage.tsx` (nouveau)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
||
|
|
import { useState, useEffect } from 'react';
|
||
|
|
import { marketplaceService } from '@/services/marketplaceService';
|
||
|
|
import { ProductDetailView, ProductDetailViewSkeleton } from '@/components/marketplace/ProductDetailView';
|
||
|
|
import { ErrorDisplay } from '@/components/ui/ErrorDisplay';
|
||
|
|
import { useCartStore } from '@/stores/cartStore';
|
||
|
|
import type { Product } from '@/types';
|
||
|
|
|
||
|
|
export function ProductDetailPage() {
|
||
|
|
const { id } = useParams<{ id: string }>();
|
||
|
|
const navigate = useNavigate();
|
||
|
|
const addItem = useCartStore((s) => s.addItem);
|
||
|
|
const [product, setProduct] = useState<Product | null>(null);
|
||
|
|
const [similar, setSimilar] = useState<Product[]>([]);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [error, setError] = useState<Error | null>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!id) return;
|
||
|
|
setLoading(true);
|
||
|
|
setError(null);
|
||
|
|
Promise.all([
|
||
|
|
marketplaceService.getProduct(id),
|
||
|
|
marketplaceService.listProducts({}, { page: 1, limit: 4 }),
|
||
|
|
])
|
||
|
|
.then(([prod, res]) => {
|
||
|
|
setProduct(prod);
|
||
|
|
setSimilar((res.products || []).filter((p) => p.id !== id).slice(0, 3));
|
||
|
|
})
|
||
|
|
.catch((e) => setError(e instanceof Error ? e : new Error(String(e))))
|
||
|
|
.finally(() => setLoading(false));
|
||
|
|
}, [id]);
|
||
|
|
|
||
|
|
if (loading) return <ProductDetailViewSkeleton />;
|
||
|
|
if (error) return <ErrorDisplay error={error} onRetry={() => window.location.reload()} />;
|
||
|
|
if (!product) return <ErrorDisplay error={new Error('Product not found')} />;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<ProductDetailView
|
||
|
|
product={product}
|
||
|
|
similarProducts={similar}
|
||
|
|
onBack={() => navigate('/marketplace')}
|
||
|
|
onAddToCart={() => addItem({ product_id: product.id, quantity: 1 })}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Fichier** : [`lazyExports.ts`](apps/web/src/components/ui/lazy-component/lazyExports.ts) — ajouter après `LazyMarketplace` :
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const LazyProductDetail = createLazyComponent(
|
||
|
|
() =>
|
||
|
|
import('@/features/marketplace/pages/ProductDetailPage').then((m) => ({
|
||
|
|
default: m.ProductDetailPage,
|
||
|
|
})),
|
||
|
|
undefined,
|
||
|
|
'Product Detail',
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
**Fichier** : [`index.ts`](apps/web/src/components/ui/lazy-component/index.ts) — ajouter `LazyProductDetail` dans les exports
|
||
|
|
|
||
|
|
**Fichier** : [`LazyComponent.tsx`](apps/web/src/components/ui/LazyComponent.tsx) — ajouter `LazyProductDetail` dans les re-exports
|
||
|
|
|
||
|
|
**Fichier** : [`routeConfig.tsx`](apps/web/src/router/routeConfig.tsx) :
|
||
|
|
- Ajouter `LazyProductDetail` dans les imports
|
||
|
|
- Ajouter la route après `/marketplace` :
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
{ path: '/marketplace/products/:id', element: wrapProtected(<LazyProductDetail />) },
|
||
|
|
```
|
||
|
|
|
||
|
|
**Commit** : `feat(marketplace): add ProductDetailPage, lazy export, route /marketplace/products/:id`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sprint 2 : Lot M1 — MSW handlers reviews & invoice
|
||
|
|
|
||
|
|
### Step 2 : MSW handlers reviews
|
||
|
|
|
||
|
|
**Fichier** : [`handlers-marketplace.ts`](apps/web/src/mocks/handlers-marketplace.ts) — ajouter après le handler `GET /marketplace/products/:id` :
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// v0.702: Product reviews
|
||
|
|
http.get('*/api/v1/marketplace/products/:id/reviews', () => {
|
||
|
|
return HttpResponse.json({
|
||
|
|
success: true,
|
||
|
|
data: {
|
||
|
|
reviews: [
|
||
|
|
{
|
||
|
|
id: 'rev-1',
|
||
|
|
product_id: 'prod-1',
|
||
|
|
buyer_id: 'user-1',
|
||
|
|
order_id: 'ord-1',
|
||
|
|
rating: 5,
|
||
|
|
comment: 'Amazing track, perfect for my project!',
|
||
|
|
created_at: new Date(Date.now() - 86400000).toISOString(),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'rev-2',
|
||
|
|
product_id: 'prod-1',
|
||
|
|
buyer_id: 'user-2',
|
||
|
|
order_id: 'ord-2',
|
||
|
|
rating: 4,
|
||
|
|
comment: 'Good quality, a bit short though.',
|
||
|
|
created_at: new Date(Date.now() - 172800000).toISOString(),
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}),
|
||
|
|
|
||
|
|
http.post('*/api/v1/marketplace/products/:id/reviews', async ({ request }) => {
|
||
|
|
const body = (await request.json()) as { rating?: number; comment?: string };
|
||
|
|
return HttpResponse.json({
|
||
|
|
success: true,
|
||
|
|
data: {
|
||
|
|
id: 'rev-new',
|
||
|
|
product_id: 'prod-1',
|
||
|
|
buyer_id: 'user-1',
|
||
|
|
order_id: 'ord-1',
|
||
|
|
rating: body.rating ?? 5,
|
||
|
|
comment: body.comment ?? '',
|
||
|
|
created_at: new Date().toISOString(),
|
||
|
|
},
|
||
|
|
}, { status: 201 });
|
||
|
|
}),
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 3 : MSW handler invoice
|
||
|
|
|
||
|
|
**Fichier** : [`handlers-marketplace.ts`](apps/web/src/mocks/handlers-marketplace.ts) — ajouter après le handler `GET /marketplace/orders/:id` :
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// v0.702: Invoice PDF download (mock blob)
|
||
|
|
http.get('*/api/v1/marketplace/orders/:id/invoice', () => {
|
||
|
|
const pdfContent = '%PDF-1.4 mock invoice content';
|
||
|
|
return new HttpResponse(pdfContent, {
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/pdf',
|
||
|
|
'Content-Disposition': 'attachment; filename="invoice.pdf"',
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}),
|
||
|
|
```
|
||
|
|
|
||
|
|
**Commit** : `feat(mocks): add MSW handlers for product reviews and invoice download`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sprint 3 : Lot T1 — Tests backend
|
||
|
|
|
||
|
|
### Step 4 : Tests reviews
|
||
|
|
|
||
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/review_test.go` (nouveau)
|
||
|
|
|
||
|
|
Cas de test (utiliser le pattern SQLite in-memory de `process_webhook_test.go`) :
|
||
|
|
|
||
|
|
- `TestCreateReview_Success` : acheteur avec licence crée une review → status 200, review enregistrée
|
||
|
|
- `TestCreateReview_InvalidRating` : rating=0 ou rating=6 → erreur
|
||
|
|
- `TestCreateReview_NotPurchased` : acheteur sans licence → `ErrReviewNotPurchased`
|
||
|
|
- `TestCreateReview_DuplicateReview` : 2ème review du même acheteur sur même produit → `ErrReviewAlreadyExists`
|
||
|
|
- `TestListReviews_Paginated` : 3 reviews, limit=2 → retourne 2 reviews, `created_at DESC`
|
||
|
|
- `TestListReviews_EmptyProduct` : produit sans reviews → slice vide, pas d'erreur
|
||
|
|
|
||
|
|
Setup commun :
|
||
|
|
- AutoMigrate: Product, Order, OrderItem, License, ProductLicense, ProductReview
|
||
|
|
- Créer product + seller + buyer + order + license pour les tests positifs
|
||
|
|
|
||
|
|
**Commit** : `test(marketplace): add product review unit tests`
|
||
|
|
|
||
|
|
### Step 5 : Tests invoices
|
||
|
|
|
||
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/invoice_test.go` (nouveau)
|
||
|
|
|
||
|
|
Cas de test :
|
||
|
|
|
||
|
|
- `TestGenerateInvoice_Success` : order avec items → PDF bytes non vide, commence par `%PDF`
|
||
|
|
- `TestGenerateInvoice_WrongBuyer` : buyer_id != order.buyer_id → erreur
|
||
|
|
- `TestGenerateInvoice_OrderNotFound` : UUID inexistant → erreur
|
||
|
|
- `TestGenerateInvoice_WithDiscount` : order avec `DiscountAmountCents > 0` → PDF inclut le rabais
|
||
|
|
|
||
|
|
Setup : AutoMigrate Order, OrderItem, Product, créer order avec items.
|
||
|
|
|
||
|
|
Note : `GenerateInvoice` utilise `github.com/go-pdf/fpdf`. Vérifier que cette dépendance est dans `go.mod`.
|
||
|
|
|
||
|
|
**Commit** : `test(marketplace): add invoice generation unit tests`
|
||
|
|
|
||
|
|
### Step 6 : Tests refunds
|
||
|
|
|
||
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/refund_test.go` (nouveau)
|
||
|
|
|
||
|
|
Cas de test :
|
||
|
|
|
||
|
|
- `TestRefundOrder_Success` : order completed avec HyperswitchPaymentID → status `refunded`, licences `revoked_at` set
|
||
|
|
- `TestRefundOrder_NotCompleted` : order `pending` → `ErrOrderNotRefundable`
|
||
|
|
- `TestRefundOrder_NoPaymentID` : order completed mais `HyperswitchPaymentID=""` → `ErrOrderNotRefundable`
|
||
|
|
- `TestRefundOrder_Forbidden` : initiatorID != buyer ni seller → `ErrRefundForbidden`
|
||
|
|
- `TestRefundOrder_SellerCanRefund` : seller initie le refund → succès
|
||
|
|
|
||
|
|
Mock `refundProvider` :
|
||
|
|
```go
|
||
|
|
type mockRefundProvider struct {
|
||
|
|
err error
|
||
|
|
}
|
||
|
|
func (m *mockRefundProvider) Refund(_ context.Context, _ string, _ *int64, _ string) error {
|
||
|
|
return m.err
|
||
|
|
}
|
||
|
|
func (m *mockRefundProvider) CreatePayment(ctx context.Context, req marketplace.PaymentRequest) (*marketplace.PaymentResponse, error) {
|
||
|
|
return &marketplace.PaymentResponse{}, nil
|
||
|
|
}
|
||
|
|
func (m *mockRefundProvider) GetPayment(ctx context.Context, paymentID string) (*marketplace.PaymentStatus, error) {
|
||
|
|
return &marketplace.PaymentStatus{}, nil
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Note : le mock doit implémenter `PaymentProvider` (interface complète) **et** `refundProvider` (interface interne).
|
||
|
|
|
||
|
|
**Commit** : `test(marketplace): add refund order unit tests`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sprint 4 : Lot S1 — Stories
|
||
|
|
|
||
|
|
### Step 7 : Story ProductDetailView connectée MSW
|
||
|
|
|
||
|
|
**Fichier** : [`ProductDetailView.stories.tsx`](apps/web/src/components/marketplace/ProductDetailView.stories.tsx) — enrichir les stories existantes
|
||
|
|
|
||
|
|
Ajouter une story `Error` et une story `WithManyReviews` :
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
/** Error state */
|
||
|
|
export const Error: Story = {
|
||
|
|
name: 'Erreur',
|
||
|
|
args: {
|
||
|
|
product: undefined as unknown as Product,
|
||
|
|
similarProducts: [],
|
||
|
|
onBack: () => {},
|
||
|
|
onAddToCart: () => {},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
Note : La story `WithReviews` existe déjà. Pas de modifications majeures nécessaires car le composant reçoit `product` en prop avec `reviews[]`.
|
||
|
|
|
||
|
|
**Commit** : `feat(storybook): enhance ProductDetailView stories with Error state`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sprint 5 : Lot D1 — Documentation
|
||
|
|
|
||
|
|
### Step 8 : Mettre à jour API_REFERENCE.md
|
||
|
|
|
||
|
|
**Fichier** : [`docs/API_REFERENCE.md`](docs/API_REFERENCE.md) — ajouter 3 sections :
|
||
|
|
|
||
|
|
**Section Reviews** (après Marketplace) :
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
## Reviews
|
||
|
|
|
||
|
|
### GET /marketplace/products/:id/reviews
|
||
|
|
|
||
|
|
List reviews for a product (public).
|
||
|
|
|
||
|
|
**Auth:** None
|
||
|
|
|
||
|
|
**Query params:** `limit` (default 20), `offset` (default 0)
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
\`\`\`bash
|
||
|
|
curl "http://localhost:8080/api/v1/marketplace/products/uuid/reviews?limit=10"
|
||
|
|
\`\`\`
|
||
|
|
|
||
|
|
**Response (200):**
|
||
|
|
\`\`\`json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"data": {
|
||
|
|
"reviews": [
|
||
|
|
{ "id": "uuid", "product_id": "uuid", "buyer_id": "uuid", "rating": 5, "comment": "Great!", "created_at": "..." }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
\`\`\`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### POST /marketplace/products/:id/reviews
|
||
|
|
|
||
|
|
Create a review (buyer only, requires purchase).
|
||
|
|
|
||
|
|
**Auth:** Bearer token (required)
|
||
|
|
|
||
|
|
**Body:**
|
||
|
|
\`\`\`json
|
||
|
|
{ "rating": 5, "comment": "Amazing track!" }
|
||
|
|
\`\`\`
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
\`\`\`bash
|
||
|
|
curl -X POST http://localhost:8080/api/v1/marketplace/products/uuid/reviews \
|
||
|
|
-H "Authorization: Bearer eyJ..." \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{"rating":5,"comment":"Amazing track!"}'
|
||
|
|
\`\`\`
|
||
|
|
```
|
||
|
|
|
||
|
|
**Section Invoices** (après Orders) :
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
## Invoices
|
||
|
|
|
||
|
|
### GET /marketplace/orders/:id/invoice
|
||
|
|
|
||
|
|
Download invoice PDF for an order (buyer only).
|
||
|
|
|
||
|
|
**Auth:** Bearer token (required)
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
\`\`\`bash
|
||
|
|
curl -o invoice.pdf http://localhost:8080/api/v1/marketplace/orders/uuid/invoice \
|
||
|
|
-H "Authorization: Bearer eyJ..."
|
||
|
|
\`\`\`
|
||
|
|
|
||
|
|
**Response:** PDF binary (`Content-Type: application/pdf`)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Section Refunds** (après Invoices) :
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
## Refunds
|
||
|
|
|
||
|
|
### POST /marketplace/orders/:id/refund
|
||
|
|
|
||
|
|
Request a refund (buyer or seller of products in order).
|
||
|
|
|
||
|
|
**Auth:** Bearer token (required)
|
||
|
|
|
||
|
|
**Body:**
|
||
|
|
\`\`\`json
|
||
|
|
{ "reason": "Not as described", "details": "optional details" }
|
||
|
|
\`\`\`
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
\`\`\`bash
|
||
|
|
curl -X POST http://localhost:8080/api/v1/marketplace/orders/uuid/refund \
|
||
|
|
-H "Authorization: Bearer eyJ..." \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{"reason":"Not as described"}'
|
||
|
|
\`\`\`
|
||
|
|
|
||
|
|
**Response (200):**
|
||
|
|
\`\`\`json
|
||
|
|
{ "success": true, "data": { "message": "Refund initiated" } }
|
||
|
|
\`\`\`
|
||
|
|
```
|
||
|
|
|
||
|
|
**Commit** : `docs: add reviews, invoices, refunds to API_REFERENCE.md`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sprint 6 : Lot DOC — Finalization & Release
|
||
|
|
|
||
|
|
### Step 9 : Build validation
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Backend
|
||
|
|
cd veza-backend-api && go build ./... && go vet ./... && go test ./... -v
|
||
|
|
|
||
|
|
# Frontend
|
||
|
|
cd apps/web && npm run build
|
||
|
|
```
|
||
|
|
|
||
|
|
**Pas de commit — validation uniquement.**
|
||
|
|
|
||
|
|
### Step 10 : CHANGELOG, PROJECT_STATE, FEATURE_STATUS
|
||
|
|
|
||
|
|
**Fichier** : [`CHANGELOG.md`](CHANGELOG.md) — ajouter en tête :
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
## [v0.702] - 2026-02-XX
|
||
|
|
|
||
|
|
### Added
|
||
|
|
- Route /marketplace/products/:id with ProductDetailPage (lazy loaded)
|
||
|
|
- MSW handlers for product reviews (GET list, POST create) and invoice download
|
||
|
|
- Unit tests: product reviews (6 tests), invoice generation (4 tests), refund order (5 tests)
|
||
|
|
- API_REFERENCE.md: documented reviews, invoices, refunds endpoints
|
||
|
|
|
||
|
|
### Changed
|
||
|
|
- ProductDetailView.stories.tsx: added Error state story
|
||
|
|
```
|
||
|
|
|
||
|
|
**Fichier** : [`docs/PROJECT_STATE.md`](docs/PROJECT_STATE.md) :
|
||
|
|
- `Dernier tag` → `v0.702`
|
||
|
|
- `Prochaine version` → `v0.703`
|
||
|
|
- Ajouter section v0.702 dans "Ce qui est livré"
|
||
|
|
|
||
|
|
**Fichier** : [`docs/FEATURE_STATUS.md`](docs/FEATURE_STATUS.md) :
|
||
|
|
- Ajouter section "Livré en v0.702"
|
||
|
|
- Mettre à jour la ligne Marketplace
|
||
|
|
|
||
|
|
**Commit** : `docs: update CHANGELOG, PROJECT_STATE, FEATURE_STATUS for v0.702`
|
||
|
|
|
||
|
|
### Step 11 : Rétrospective + placeholder v0.703
|
||
|
|
|
||
|
|
**Fichier** : `docs/RETROSPECTIVE_V0702.md` (nouveau)
|
||
|
|
|
||
|
|
**Fichier** : `docs/V0_703_RELEASE_SCOPE.md` (nouveau, placeholder)
|
||
|
|
|
||
|
|
**Fichier** : [`docs/SCOPE_CONTROL.md`](docs/SCOPE_CONTROL.md) :
|
||
|
|
- `Référence active` → `V0_703_RELEASE_SCOPE.md`
|
||
|
|
- `Version précédente` → `V0_702_RELEASE_SCOPE.md` (archive)
|
||
|
|
- Historique : `v0.702 : Phase 7 — Reviews, Factures, Remboursements — taguée`
|
||
|
|
- Remplacer toutes les occurrences de `v0.702` par `v0.703` et `v0.703` par `v0.704` dans les règles
|
||
|
|
|
||
|
|
**Fichier** : [`.cursorrules`](.cursorrules) :
|
||
|
|
- Scope `v0.703`
|
||
|
|
|
||
|
|
**Commit** : `docs: add RETROSPECTIVE_V0702, placeholder V0_703, update SCOPE_CONTROL`
|
||
|
|
|
||
|
|
### Step 12 : Archiver V0_702_RELEASE_SCOPE + tag final
|
||
|
|
|
||
|
|
**Commande** : `mv docs/V0_702_RELEASE_SCOPE.md docs/archive/V0_702_RELEASE_SCOPE.md`
|
||
|
|
|
||
|
|
**Commit** : `chore(docs): archive V0_702_RELEASE_SCOPE`
|
||
|
|
|
||
|
|
**Tag** : `git tag v0.702`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Dépendances entre steps
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TD
|
||
|
|
S1[Step1 ProductDetailPage] --> S9[Step9 Build]
|
||
|
|
S2[Step2 MSW reviews] --> S9
|
||
|
|
S3[Step3 MSW invoice] --> S9
|
||
|
|
S4[Step4 Tests reviews] --> S9
|
||
|
|
S5[Step5 Tests invoices] --> S9
|
||
|
|
S6[Step6 Tests refunds] --> S9
|
||
|
|
S7[Step7 Stories] --> S9
|
||
|
|
S8[Step8 APIRef] --> S10[Step10 Changelog]
|
||
|
|
S9 --> S10
|
||
|
|
S10 --> S11[Step11 Retro]
|
||
|
|
S11 --> S12[Step12 Tag]
|
||
|
|
```
|
||
|
|
|
||
|
|
Les Steps 1-8 sont indépendants et peuvent être réalisés en parallèle.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Validation finale
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Backend
|
||
|
|
cd veza-backend-api && go build ./... && go vet ./... && go test ./... -v
|
||
|
|
|
||
|
|
# Frontend
|
||
|
|
cd apps/web && npm run build && npm run typecheck
|
||
|
|
|
||
|
|
# Smoke test
|
||
|
|
# 1. GET /marketplace/products/:id → 200 + product data
|
||
|
|
# 2. POST /marketplace/products/:id/reviews → 201 (avec license)
|
||
|
|
# 3. GET /marketplace/orders/:id/invoice → PDF download
|
||
|
|
# 4. POST /marketplace/orders/:id/refund → 200, order.status = refunded
|
||
|
|
```
|