# Backend Performance-Analyse: Sell-Funktionen

## Identifizierte Performance-Probleme

### 1. **N+1 Query Problem in `sellAllProducts()`**

**Problem:**
Die Funktion `sellAllProducts()` macht für jedes Inventory-Item mehrere separate Datenbankabfragen:

1. **Erste Schleife (Zeile 1702-1711):**
   - `calcRegionalSellPrice()` → macht `TownProductWorth.findOne()` für jedes Item
   - `getCumulativeTaxPercentWithExemptions()` → macht mehrere Queries pro Item:
     - `FalukantCharacter.findOne()`
     - `PoliticalOffice.findAll()` mit Includes
     - Rekursive SQL-Query für Steuerberechnung
   - `addSellItem()` → macht `Branch.findOne()` und `DaySell.findOne()`/`create()` für jedes Item

2. **Zweite Schleife (Zeile 1714-1724):**
   - `RegionData.findOne()` für jedes Item
   - `getCumulativeTaxPercent()` → rekursive SQL-Query für jedes Item
   - `calcRegionalSellPrice()` → erneut `TownProductWorth.findOne()` für jedes Item

**Beispiel:** Bei 10 Items werden gemacht:
- 10x `TownProductWorth.findOne()` (2x pro Item)
- 10x `RegionData.findOne()`
- 10x `getCumulativeTaxPercentWithExemptions()` (mit mehreren Queries)
- 10x `getCumulativeTaxPercent()` (rekursive SQL)
- 10x `addSellItem()` (mit 2 Queries pro Item)
- = **~70+ Datenbankabfragen für 10 Items**

### 2. **Ineffiziente `addSellItem()` Implementierung**

**Problem:**
- Wird für jedes Item einzeln aufgerufen
- Macht `Branch.findOne()` für jedes Item (könnte gecacht werden)
- `DaySell.findOne()` und `create()`/`update()` für jedes Item

**Lösung:** Batch-Operation implementieren, die alle DaySell Einträge auf einmal verarbeitet.

### 3. **Doppelte Berechnungen in `sellAllProducts()`**

**Problem:**
- Preis wird zweimal berechnet (Zeile 1705 und 1718)
- Steuer wird zweimal berechnet (Zeile 1706 und 1717)
- `calcRegionalSellPrice()` wird zweimal aufgerufen mit denselben Parametern

### 4. **Fehlende Indizes**

**Potenzielle fehlende Indizes:**
- `falukant_data.town_product_worth(product_id, region_id)` - sollte unique sein
- `falukant_data.inventory(stock_id, product_id, quality)` - für schnelle Lookups
- `falukant_data.knowledge(character_id, product_id)` - für Knowledge-Lookups
- `falukant_data.political_office(character_id)` - für Steuerbefreiungen

### 5. **Ineffiziente `getCumulativeTaxPercentWithExemptions()`**

**Problem:**
- Lädt alle PoliticalOffices jedes Mal neu, auch wenn sich nichts geändert hat
- Macht komplexe rekursive SQL-Query für jedes Item separat
- Könnte gecacht werden (z.B. pro User+Region Kombination)

## Empfohlene Optimierungen

### 1. **Batch-Loading für `sellAllProducts()`**

```javascript
async sellAllProducts(hashedUserId, branchId) {
    // ... existing code ...
    
    // Batch-Load alle benötigten Daten VOR den Schleifen
    const regionIds = [...new Set(inventory.map(item => item.stock.branch.regionId))];
    const productIds = [...new Set(inventory.map(item => item.productType.id))];
    
    // 1. Lade alle TownProductWorth Einträge auf einmal
    const townWorths = await TownProductWorth.findAll({
        where: {
            productId: { [Op.in]: productIds },
            regionId: { [Op.in]: regionIds }
        }
    });
    const worthMap = new Map();
    townWorths.forEach(tw => {
        worthMap.set(`${tw.productId}-${tw.regionId}`, tw.worthPercent);
    });
    
    // 2. Lade alle RegionData auf einmal
    const regions = await RegionData.findAll({
        where: { id: { [Op.in]: regionIds } }
    });
    const regionMap = new Map(regions.map(r => [r.id, r]));
    
    // 3. Berechne Steuern für alle Regionen auf einmal
    const taxMap = new Map();
    for (const regionId of regionIds) {
        const tax = await getCumulativeTaxPercentWithExemptions(falukantUser.id, regionId);
        taxMap.set(regionId, tax);
    }
    
    // 4. Berechne Preise und Steuern in einer Schleife
    const sellItems = [];
    for (const item of inventory) {
        const regionId = item.stock.branch.regionId;
        const worthPercent = worthMap.get(`${item.productType.id}-${regionId}`) || 50;
        const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0;
        const pricePerUnit = calcRegionalSellPrice(item.productType, knowledgeVal, regionId, worthPercent);
        const cumulativeTax = taxMap.get(regionId);
        // ... rest of calculation ...
        sellItems.push({ branchId: item.stock.branch.id, productId: item.productType.id, quantity: item.quantity });
    }
    
    // 5. Batch-Update DaySell Einträge
    await this.addSellItemsBatch(sellItems);
    
    // ... rest of code ...
}
```

### 2. **Batch-Operation für `addSellItem()`**

```javascript
async addSellItemsBatch(sellItems) {
    // Gruppiere nach (regionId, productId, sellerId)
    const grouped = new Map();
    for (const item of sellItems) {
        const branch = await Branch.findByPk(item.branchId);
        if (!branch) continue;
        
        const key = `${branch.regionId}-${item.productId}-${item.sellerId}`;
        if (!grouped.has(key)) {
            grouped.set(key, {
                regionId: branch.regionId,
                productId: item.productId,
                sellerId: item.sellerId,
                quantity: 0
            });
        }
        grouped.get(key).quantity += item.quantity;
    }
    
    // Batch-Update oder Create
    for (const [key, data] of grouped) {
        const [daySell, created] = await DaySell.findOrCreate({
            where: {
                regionId: data.regionId,
                productId: data.productId,
                sellerId: data.sellerId
            },
            defaults: { quantity: data.quantity }
        });
        
        if (!created) {
            daySell.quantity += data.quantity;
            await daySell.save();
        }
    }
}
```

### 3. **Caching für `getCumulativeTaxPercentWithExemptions()`**

```javascript
// Cache für Steuerberechnungen (z.B. 5 Minuten)
const taxCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 Minuten

async function getCumulativeTaxPercentWithExemptions(userId, regionId) {
    const cacheKey = `${userId}-${regionId}`;
    const cached = taxCache.get(cacheKey);
    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
        return cached.value;
    }
    
    // ... existing calculation ...
    
    taxCache.set(cacheKey, { value: tax, timestamp: Date.now() });
    return tax;
}
```

### 4. **Optimierte `calcRegionalSellPrice()`**

```javascript
async function calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent = null) {
    // Wenn worthPercent nicht übergeben wurde UND wir es nicht aus dem Cache haben,
    // dann hole es aus der DB
    if (worthPercent === null) {
        const townWorth = await TownProductWorth.findOne({
            where: { productId: product.id, regionId: regionId }
        });
        worthPercent = townWorth?.worthPercent || 50;
    }
    
    // ... rest of calculation ...
}
```

### 5. **Datenbank-Indizes hinzufügen**

```sql
-- Index für town_product_worth (sollte unique sein)
CREATE UNIQUE INDEX IF NOT EXISTS idx_town_product_worth_product_region 
ON falukant_data.town_product_worth(product_id, region_id);

-- Index für inventory Lookups
CREATE INDEX IF NOT EXISTS idx_inventory_stock_product_quality 
ON falukant_data.inventory(stock_id, product_id, quality);

-- Index für knowledge Lookups
CREATE INDEX IF NOT EXISTS idx_knowledge_character_product 
ON falukant_data.knowledge(character_id, product_id);

-- Index für political_office Lookups
CREATE INDEX IF NOT EXISTS idx_political_office_character 
ON falukant_data.political_office(character_id);
```

## Geschätzter Performance-Gewinn

- **Vorher:** ~70+ Queries für 10 Items
- **Nachher:** ~15-20 Queries für 10 Items (Batch-Loading + Caching)
- **Geschätzte Verbesserung:** 70-80% weniger Datenbankabfragen

## Priorität

1. **Hoch:** Batch-Loading für `sellAllProducts()` (größter Impact)
2. **Hoch:** Batch-Operation für `addSellItem()`
3. **Mittel:** Caching für Steuerberechnungen
4. **Mittel:** Datenbank-Indizes
5. **Niedrig:** Doppelte Berechnungen entfernen

