09-moteur-tarification

<?php
class PricingEngine {
    private array $config;
    
    public function __construct() {
        $this->config = json_decode(file_get_contents('/etc/pricing.json'), true);
    }
    
    public function calculatePrice(Product $product, Customer $customer, int $quantity): float {
        $basePrice = $product->getPrice();
        $finalPrice = $basePrice * $quantity;
        
        // Application des remises client
        $customerType = $customer->getType();
        if($customerType === 'vip') {
            $finalPrice *= 0.85;
        } elseif($customerType === 'regular' && $customer->getYearsOfMembership() > 2) {
            $finalPrice *= 0.90;
        } elseif($customerType === 'new') {
            if($customer->hasReferralCode()) {
                $finalPrice *= 0.95;
            }
        }
        
        // Remise volume
        if($quantity >= 100) {
            $finalPrice *= 0.80;
        } elseif($quantity >= 50) {
            $finalPrice *= 0.85;
        } elseif($quantity >= 20) {
            $finalPrice *= 0.90;
        } elseif($quantity >= 10) {
            $finalPrice *= 0.95;
        }
        
        // Remise catégorie produit
        $category = $product->getCategory();
        if($category === 'electronics' && $this->isBlackFriday()) {
            $finalPrice *= 0.70;
        } elseif($category === 'clothing' && $this->isSummer()) {
            $finalPrice *= 0.80;
        } elseif($category === 'books') {
            $finalPrice *= 0.95; // Toujours 5% sur les livres
        }
        
        // Prix spéciaux pour zones géographiques
        $country = $customer->getCountry();
        if($country === 'FR' && $product->isMadeInFrance()) {
            $finalPrice *= 0.90;
        } elseif(in_array($country, ['DE', 'IT', 'ES'])) {
            $finalPrice *= 1.05; // Frais de douane
        }
        
        // Programmes de fidélité
        $loyaltyPoints = $customer->getLoyaltyPoints();
        if($loyaltyPoints >= 1000) {
            $discount = min($loyaltyPoints / 100, $finalPrice * 0.50);
            $finalPrice -= $discount;
            
            $db = new PDO('mysql:host=localhost;dbname=shop','root','');
            $stmt = $db->prepare('UPDATE customers SET loyalty_points = loyalty_points - ? WHERE id = ?');
            $stmt->execute([$discount * 100, $customer->getId()]);
        }
        
        // Promotions temporaires
        $promos = $this->getActivePromotions();
        foreach($promos as $promo) {
            if($this->promoApplies($promo, $product, $customer)) {
                $finalPrice *= (1 - $promo['discount_percent'] / 100);
            }
        }
        
        // Logging et analytics
        $db = new PDO('mysql:host=localhost;dbname=shop','root','');
        $stmt = $db->prepare('INSERT INTO price_calculations (product_id,customer_id,base_price,final_price,timestamp) VALUES (?,?,?,?,?)');
        $stmt->execute([$product->getId(), $customer->getId(), $basePrice, $finalPrice, time()]);
        
        // Notification si grosse commande
        if($finalPrice > 10000) {
            mail('sales@example.com', 'Large order', "Customer {$customer->getId()} is ordering for {$finalPrice} EUR");
        }
        
        return round($finalPrice, 2);
    }
    
    private function isBlackFriday(): bool {
        $date = new DateTime();
        return $date->format('m') === '11' && $date->format('d') >= '20';
    }
    
    private function isSummer(): bool {
        $month = (int)(new DateTime())->format('m');
        return $month >= 6 && $month <= 8;
    }
    
    private function getActivePromotions(): array {
        $db = new PDO('mysql:host=localhost;dbname=shop','root','');
        $stmt = $db->query('SELECT * FROM promotions WHERE active = 1 AND start_date <= NOW() AND end_date >= NOW()');
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    
    private function promoApplies(array $promo, Product $product, Customer $customer): bool {
        if(isset($promo['product_category']) && $product->getCategory() !== $promo['product_category']) {
            return false;
        }
        if(isset($promo['customer_type']) && $customer->getType() !== $promo['customer_type']) {
            return false;
        }
        return true;
    }
}

class Product {
    public function getId(): int { return 1; }
    public function getPrice(): float { return 100.0; }
    public function getCategory(): string { return 'electronics'; }
    public function isMadeInFrance(): bool { return false; }
}

class Customer {
    public function getId(): int { return 1; }
    public function getType(): string { return 'regular'; }
    public function getYearsOfMembership(): int { return 3; }
    public function hasReferralCode(): bool { return false; }
    public function getCountry(): string { return 'FR'; }
    public function getLoyaltyPoints(): int { return 500; }
}

/*
=== USER STORIES ===

US1: En tant que marketing manager, je veux ajouter un nouveau type de remise 
     "membre premium" avec 20% de réduction sur tout, sans modifier le code existant.

US2: En tant que développeur, je veux tester la logique de calcul des remises 
     volume indépendamment des autres règles de pricing.

US3: En tant que product owner, je veux que les règles de tarification soient 
     configurables via une interface admin plutôt qu'en dur dans le code.

US4: En tant que data analyst, je veux envoyer les logs de pricing vers Google 
     Analytics au lieu de la BDD MySQL.

US5: En tant que business analyst, je veux pouvoir simuler différents scénarios 
     de pricing sans appliquer réellement les remises et sans notifier personne.

US6: En tant que international manager, je veux ajouter des règles spécifiques 
     par pays (taxes, frais, remises locales) de manière extensible.

US7: En tant que compliance officer, je veux m'assurer que le cumul des remises 
     ne dépasse jamais 70% du prix de base, quelle que soit la combinaison de règles.

US8: En tant que product manager, je veux pouvoir A/B tester différentes stratégies 
     de pricing sur des cohortes de clients sans dupliquer le code.
*/